XSL service is running in the server at a certain port, a dockerfile was provided which was cool, it helped me a lot in the debugging process, in the container environment I had no previous knowledge of xslt which took me more time in this challenge than it should.
Solution
The seed is not set in random so we can abuse it by doing very quick requests to get the same numbers
Identify which plate prints the flag
Use the plates in a certain order to achieve the condition necessary to print the flag
Walk-through
So I started by analysing the docker file, the container is using nsjail to isolate the /usr/bin/xalan service as we can see bellow:
Setting up the Docker container
1 2 3 4 5
FROM tsuro/nsjail RUN apt-get install -y xalan COPY challenge.min.xslt /home/user/ #COPY tmpflag /flag CMD /bin/sh -c "/usr/bin/setup_cgroups.sh && su user -c '/usr/bin/nsjail -Ml --port 1337 --chroot / --user 1000 --group 1000 --cgroup_mem_max 209715200 --cgroup_pids_max 100 --cgroup_cpu_ms_per_sec 100 --rlimit_as max --rlimit_cpu max --rlimit_nofile max --rlimit_nproc max -- /usr/bin/stdbuf -i0 -o0 -e0 /usr/bin/maybe_pow.sh /usr/bin/xalan -xsl /home/user/challenge.min.xslt'"
Eventually I had to fix a line in the docker file at:
1
COPY challenge/challenge.min.xslt /home/user/
The challenge.min.xslt was located in the root directory after extracting the files, so I ended up changing this into:
1
COPY challenge.min.xslt /home/user/
So lets start to build the image with docker build:
$ sudo docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE permute latest 1603e833509a 23 hours ago 147MB juggle latest b4c26a022c1e 33 hours ago 205MB tsuro/nsjail latest 8774d7aad732 2 days ago 156MB adalovelace/nodejs latest c2a6e42b25c8 2 days ago 63.7MB node 6-alpine c45d42c157e3 2 days ago 55.6MB nginx latest 02256cfb0e4b 2 days ago 109MB alpine latest 3f53bb00af94 8 days ago 4.41MB centos 7 1e1148e4cc2c 3 weeks ago 202MB postgres latest f9b577fb1ed6 4 weeks ago 311MB thecolonial/police-quest 1.0 0ea244849eb0 4 weeks ago 345MB ubuntu 14.04 f17b6a61de28 5 weeks ago 188MB mysql latest f991c20cb508 6 weeks ago 486MB httpd latest 2a51bb06dc8b 6 weeks ago 132MB debian latest 4879790bd60d 6 weeks ago 101MB seccon_pwn latest 3749dfe7031c 7 weeks ago 2.06GB skysider/pwndocker latest a24dde07a423 7 weeks ago 2.02GB elasticsearch 2 5e9d896dc62c 3 months ago 479MB postgres 9.6.2 b3b8a2229953 19 months ago 267MB postgres 9.6.1 4023a747a01a 23 months ago 265MB
As we can see in the list the juggle image was created (the name we gave it).
With this we can start running the container by just using docker container run:
After this a bash console will pop-up in the container unfortunately the command in the end of the docker didn’t work maybe something related with nsjail, I don’t know maybe I did something wrong when I was trying to connect to the service, I even tryed to rerun the command inside the container but it always gave me an error, so to not loose much time identifying it I setted up very quiclky a service with socat, socat isn’t installed in this container so I needed to install it via apt-get:
Since I forwarded the port 1337 from the host to the container port 1337 with the parameter -p in the docker run command. Now I could connect from the host directly to the localhost with ncat localhost 1337 without getting any errors from the server.
XML format analysis
So it’s time to start analysing the challenge.min.xslt file, this file was minimized so I searched for an online tool to beautify to make it more readable, so lets start with the beginning:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheetversion="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://exslt.org/math" xmlns:exsl="http://exslt.org/common"exclude-result-prefixes="xsl math exsl"> <xsl:templatematch="/meal"> <all> <xsl:iftest="count(//plate) > 300"> <xsl:messageterminate="yes">You do not have enough money to buy that much food</xsl:message> </xsl:if> <xsl:variablename="chef-drinks"> <value> <xsl:value-ofselect="round(math:random() * 4294967296)"/> </value> <value> <xsl:value-ofselect="round(math:random() * 4294967296)"/> </value> <value> <xsl:value-ofselect="round(math:random() * 4294967296)"/> </value> <value> <xsl:value-ofselect="round(math:random() * 4294967296)"/> </value> <value> <xsl:value-ofselect="round(math:random() * 4294967296)"/> </value> </xsl:variable> <xsl:call-templatename="consume-meal"> <xsl:with-paramname="chef-drinks"select="exsl:node-set($chef-drinks)//value"/> <xsl:with-paramname="food-eaten"select="1"/> <xsl:with-paramname="course"select="course[position() = 1]/plate"/> <xsl:with-paramname="drinks"select="state/drinks"/> </xsl:call-template> </all> </xsl:template> <xsl:templatename="consume-meal"> <xsl:paramname="chef-drinks"/> <xsl:paramname="food-eaten"/> <xsl:paramname="course"/> <xsl:paramname="drinks"/> ...
So if you don’t know about xslt is a language that parses a xml document, and with that you can do things like converting that xml into html in a very easy way. Right at the beginning we can see what kind of structure of xml they want :
1
<xsl:templatematch="/meal">
The match parameter will look in the xml document from the root for a tag , from this you can look up into the elements inside of it. Now right after setting this an variable is declared with some random numbers (drinks), variables like in any other language are used to store values to be used later, in xslt is no different.
This random implementation looks already suspicious, looks it it doesn’t even have any kind of seed, and if it has a seed it probably is the current time. Now right after declaring this drinks with random numbers it does a template call as you can see bellow:
You can think of this like it’s a function call in a normal language, the name of the function is consume-meal and its arguments are right below, the declaration of consume-meal is just below this code, but before doing that lets analyse the selects for the arguments since they give us hints about how the xml document should be built.
so exsl:node-set will just grab all the values from the variable $chef-drinks and pass it into the function as it was an array, explaining the //value part, the first / is referencing the the tag of the variable <xsl:variable name=”chef-drinks”> and the /value is referencing to the \<value> tag, this extracts the variable random number drinks and sets them as an array to be used in the template “function”.
So for the second parameter:
1
<xsl:with-paramname="food-eaten"select="1"/>
The food-eaten parameter is initialized into 1 and this an integer variable that ill keep track of the number of plates you consume, after analysing consume-meal you will see that there’s a limit of plates you can eat that’s why they are tracking this number here.
Since we are inside a template tag match meal, by analysing this select it looks it inside the tag meal, our xml will need to have a course tag (\<course>) and plate tags inside of it, this works like a hierarchy from the left to right, the part [position() = 1] is just selecting the first plate inside the course tag, an example xml could be like this:
And now finally analysing the “function” consume-meal:
1 2 3 4 5 6 7 8 9 10 11
<xsl:templatename="consume-meal"> <xsl:paramname="chef-drinks"/> <xsl:paramname="food-eaten"/> <xsl:paramname="course"/> <xsl:paramname="drinks"/> <xsl:iftest="$food-eaten > 30000"> <xsl:messageterminate="yes">You ate too much and died</xsl:message> </xsl:if> <xsl:iftest="count($drinks) > 200"> <xsl:messageterminate="yes">You cannot drink that much</xsl:message> </xsl:if>
Right at the beginning the parameters of the “function” are being declared, and two ifs are checking if we ate more than 30k plates or more than 200 drinks, so if we eat of drink more than that , the function terminates and “prints” a message to the user.
After this we come up with a new if, it checks if the number of plates is higher than 0, and a new tag xsl:choose, which is like a switch-case from the c language the when tags are the case clauses, and it only enters it if the condition is true of course. Right before the choose two variables are being initialized, c and r , the variable c will get the head (first element) of the plate list inside the tag course, and r will be setted into the rest plates of the list, position()>1 will select all the plates above the position 1.
I will only explain 3 clauses, since my solution only uses 3 it makes sense to only explain these ones.
Analysing the first case-clause:
1
<xsl:whentest="count($c/宫保鸡丁) = 1">
We can already update out XML into this, since $c is a plate we will need a new tag which are named into this weird names, if we want to enter into this clause:
Now analysing the code when we enter this clause 宫保鸡丁 , as we can see below it only prints the current chef drinks and the current drinks, in the end is recalling consume-meal, this will form a recursive function that will iterate all the plates created in the course tag, as we can see the parameters passed the only thing that changes is the $course the $r is passed into this parameter so we don’t get stuck in a infinite loop which is the usually the thing you do when creating a recursive function, oh the other change is obvious the incrementing of the variable food-eaten.
Now analysing the code when we enter this clause दाल, we can see it will print and select a document “/flag” which will contain the flag we need, perhaps we need satisfy the condition count($chef-drinks) = 0, the chef-drinks is initialized with 5 random numbers (chef-drinks) so we need to find a clause that removes items from $chef-drinks so we reach the count of 0.
Now analysing the code when we enter this clause Борщ, the first drink in $drinks (drinks declared inside the state tag) is setted into arg0 variable, now analysing the recalling parameters we can see chef-drinks is being modified:
1
<xsl:with-paramname="chef-drinks"select="$chef-drinks[position() > 1 or $chef-drinks[1] != $arg0]"/>
The select will remove the head of $chef-drinks based on a condition position() > 1 or $chef-drinks[1] != $arg0, so to make this condition work we need either for both condition to be both false, or to at least position() > 1 to be true, unfortunately position() > 1 will never be true in this situation because position() will return the current position order in xml and since it’s not associated into any tag it will always return 1 because is associated to the root element meal, 1 > 1 is always false so we need to make $chef-drinks[1] != $arg0 to return false as well, the $chef-drinks are declared as random in the begining but as I said before the randomness is implemented in a wrong way, if we do fast requests we can get the same numbers as we can analyse it here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
$ ncat 35.246.237.11 1 < solution.xml Reading input document from stdin... XSLT message: 2790719341256416733922614505473645015994193916258 (Occurred in entity 'file:///home/user/challenge.min.xslt', at line 1, column 1.) Source tree node: meal. XSLT message: (Occurred in entity 'file:///home/user/challenge.min.xslt', at line 1, column 1.) Source tree node: meal. <?xml version="1.0" encoding="UTF-8"?><all/>⏎
$ ncat 35.246.237.11 1 < solution.xml Reading input document from stdin... XSLT message: 2790719341256416733922614505473645015994193916258 (Occurred in entity 'file:///home/user/challenge.min.xslt', at line 1, column 1.) Source tree node: meal. XSLT message: (Occurred in entity 'file:///home/user/challenge.min.xslt', at line 1, column 1.) Source tree node: meal. <?xml version="1.0" encoding="UTF-8"?><all/>
The only case-clause that prints the $chef-drinks for us is 宫保鸡丁 , so the xml used to print the samples above was:
But we ran into a problem here right? as you can see it prints chef-drinks into a single number, 2790719341256416733922614505473645015994193916258, it looks like the message printing is joining all the 5 elements into a one number, for example if the random numbers were 1,2,3,4,5 the number that would be printed is 12345 , the problem is sometimes each drink will have a different length and we don’t have any way to tell which length each element has, so I decided to analyse a lot of occurrences.
To make sure the length of each $chef-drink I decided to modify a little bit the challenge.min.xslt file in my docker container so I could print those numbers as well, so right after the declared parameters in consume-meal I added this line:
1
<xsl:copy-ofselect="$chef-drinks" />
So at every iteration of consumer-meals the $chef-drinks will be printed like this:
1 2 3 4 5 6 7
$ ncat localhost 1337 < solution.xml Reading input document from stdin... XSLT message: 993763458325537011481963110810288894381216985659 (Occurred in entity 'file:///home/user/challenge.min.xslt', at line 65, column 65.) Source tree node: meal. XSLT message: (Occurred in entity 'file:///home/user/challenge.min.xslt', at line 70, column 70.) Source tree node: meal. <?xml version="1.0" encoding="UTF-8"?><all><value>993763458</value><value>3255370114</value><value>819631108</value><value>1028889438</value><value>1216985659</value><value>993763458</value><value>3255370114</value><value>819631108</value><value>1028889438</value><value>1216985659</value></all>
So after this I did this bash script:
1 2 3 4 5 6
##35.246.237.11 1 for i in {1..1000} do var1=$(ncat localhost 1337 < solution.xml | grep -Poh '\d{4,}') echo$var1; done
Running it into a file:
1
$ bash rofl.sh > out
So after saving the outputs into a file I got something like this:
From the outputs I analysed that when the single number had a length of 50, the chef-drinks would always have an equal length of 10, the other lengths diverged into different things so I decided to only extract the numbers if the single number has a length of 50. So now we need to create a xml file according with the info we just got, but it needs to be done dynamically at least of the part of the drinks, because we need to be really fast on the requests to get the same number it’s only possible if you do in a program. So the structure the xml solution we want to form needs to be something like this:
We need 5 plates of Борщ because we need to reduce the count of chef-drinks to zero, and each iteration of Борщ will reduce the $chef-drinks by one, the final plate दाल is to finally print the flag, so I wrote two scripts to do this the first one is a simplified version of my bash script:
import subprocess import os import xml.etree.ElementTree as ET tree = ET.parse('solution2.xml') root = tree.getroot()
for x in xrange(100): a = subprocess.check_output(['bash','rofl.sh']).strip().split(' ') if (len(a[0]) == 50): state = root.getchildren()[1] for i in range(0,50,10): b = ET.SubElement(state,'drinks') b.text = a[0][i:i+10] break # create a new XML file with the results mydata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ET.tostring(root, encoding="utf-8") +'\n\n' myfile = open("items2.xml", "w") myfile.write(mydata)