nctf-新知识-get

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来作为渲染引擎的。其中渲染的函数中有这样的存在:

{{content}}

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

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

几个魔术方法

__class__  返回类型所属的对象
__mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__   返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的

__subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__  类的初始化方法
__globals__  对包含函数全局变量的字典的引用

大抵如下:
nctf-新知识-get_第1张图片
python

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

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

一个读etc/passwd的利用

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

''.__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:

nctf-新知识-get_第2张图片
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脚本如下:



]>
&b;

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


]>

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

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

"; 
} 
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'."
"; } 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中的


nctf-新知识-get_第3张图片
===
nctf-新知识-get_第4张图片
==

其中严格类型基本不可能绕过,所以根本不需要考虑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发现了:


nctf-新知识-get_第5张图片
string to number
nctf-新知识-get_第6张图片
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)