ATutor是一个开源基于Web的学习管理系统(LCMS),2.2.4是目前的最新版本,我汇总了所有CVE进行复现,进行黑白盒测试,对国外站点PHP代码审计的学习有所帮助。
任意文件上传
利用条件
l 获得一个有教师权限的用户(即可以创建和管理课程)
l 版本为2.2.4
利用教师用户新建一个课程
进入课程,找到文件管理界面
这里是上传点,存在任意文件上传漏洞,原因是后端采用后缀名黑名单过滤,过滤不严谨,可以修改后缀名为phP,或者phtml进行绕过
通过burpsuite进行抓包,发现上传点为mods/_core/file_manager/upload.php
因此,我们对mods/_core/file_manager/upload.php进行审计
这是一个常规的文件上传代码,重点看处理过滤的部分,代码中对上传的文件有两处过滤
1. 一个是对文件名进行str_replace函数过滤,处理掉了文件名中的特殊字符,这个无所谓
2. 另一个对文件后缀名的变量$ext,进行in_array函数黑名单判断,检测是否包含非法的后缀名,其中,$IllegalExtentions变量是后缀的黑名单
我们全局搜索下$IllegalExtentions变量具体的值
我写了一个demo,来测试in_array是否大小写敏感
经过测试,该黑名单只对小写进行过滤,因此可以大小写转换phP,进行绕过,同时黑名单过滤不严谨,用php其他可以被解析的后缀也可以绕过,以下是PHP其他的后缀名:.php3 .php4 .php5 .php7 .php8 .pht .phar .phpt .pgif .phtml .phtm
将webshell上传成功后,怎么知道文件的位置在哪里呢?
通过审计,可以知道,文件被move_uploaded_file函数上传到了
$path.$_FILES['uploadedfile']['name'],其中“.”是PHP中的拼接符号,前面的$path变量是目录的值,后面的$_FILES['uploadedfile']['name']其实就是文件名的值
我们跟进下,$path的值具体是什么
由此可知,$path的值是由AT_CONTENT_DIR和$_SESSION['course_id']以及$_POST['pathext']这三个值拼接成的,我们一个个来找
看了下我们post的请求,pathext是为空的
AT_CONTENT_DIR中,目录位置为atutor/content,前面的是它的WEB目录绝对路径
course_id的值在自定义函数add_update_course中
因为add_update_course函数最后会返回course_id的值,所以我们只用全局搜索哪个页面调用了该函数就行了
mods_core\courses\users\create_course.php页面调用了该函数,并将函数返回的值,赋给了$errors变量,因为course_id是一个大于0的整数,所以$errors的if判断肯定是为true的,即$errors!=False,最后将$errors(也就是course_id的值)通过header响应头的Location字段(即重定向的URL)输出,所以这里我们需要通过burpsuite抓包,将其放入repeater模块中,查看响应包的Location字段
回到开头,我们利用教师用户新建一个课程
通过burpsuite抓包,发现返回了course_id
大功告成,文件上传位置变量$path的值是由AT_CONTENT_DIR和$_SESSION['course_id']以及$_POST['pathext']这三个值拼接成的,因为AT_CONTENT_DIR为atutor/content,$_SESSION['course_id']为2,$_POST['pathext']为空,所以上传位置在
http://xx.com/atutor/content/2/xx.php
任意文件上传
利用条件
l 获得管理员权限用户
l 版本为2.2.4
漏洞的位置在mods/_core/languages/language_import.php,访问要先登录管理员权限用户,其他普通用户打不开
这里又是一个上传点,通过burpsuite进行抓包,发现上传点依然是language_import.php
我们对mods/_core/languages/language_import.php进行审计
我们上传的文件会传到$languageManager这个类的import函数里面
因为language_import.php包含了vitals.inc.php文件,所以$languageManager变量在vitals.inc.php里,可以看到变量$languageManager就是类LanguageManager
我们再跟进LanguageManager类的import函数
重点看$archive = new PclZip($filename); 其中PclZip是一个对压缩包进行处理的类,$filename是我们在函数中传进来的参数,所以说我们上传的文件必须是一个压缩包
可以看到,后面$archive->extract( PCLZIP_OPT_PATH, $import_path),即我们上传的文件被解压到了$import_path变量(应该是一个目录的地址)
我们上下文找下$import_path变量的值,发现其值是AT_CONTENT_DIR .和'import/'拼接起来的, AT_CONTENT_DIR中,目录位置为atutor/content,前面的是它的WEB目录绝对路径,所以$import_path的值应该为atutor/content/import,即上传文件的目录
我们将poc.php文件,进行zip压缩后,将压缩包上传
访问上文中我们总结出来的上传目录atutor/content/import
即http://xx.com/atutor/content/import/poc.php
访问控制缺陷(导致可修改任意用户密码)
利用条件
l 版本为2.2.4
存在漏洞的地方在password_reminder.php,但是要携带特定的参数才能触发漏洞
我们先审计下password_reminder.php,进入到密码找回的判断逻辑
密码找回是会发送个链接到你用户绑定的邮箱的,我们触发漏洞的参数,就是去猜测找回密码的链接的参数的值,所以必须要代码审计
首先要设置参数id、g、h,才能进入if判断里面,其中参数id就是用户的id值,参数g是密码找回发送链接到你邮箱的时间,参数h是sha1(参数id+参数g+用户的密码md5),计算出sha1后,再取其从第五个数开始的后十五位
上面看不懂也没关系,我们一步步来,变量$current = intval(((time()/60)/60)/24);我用本地PHP环境跑了一下,得到变量$current当前的值为19524
$expiry_date = $_REQUEST['g'] + AT_PASSWORD_REMINDER_EXPIRY;
其中AT_PASSWORD_REMINDER_EXPIRY是常量,值为2,$expiry_date=19524+2,就是19526
参数g的值则为19524
if ($current > $expiry_date),就会exit退出,所以$expiry_date要大于$current
$row = queryDB($sql, array(TABLE_PREFIX, $_REQUEST['id']), TRUE); 为了确保用户是存在的,我们将参数id暂时设为1
if ($_REQUEST['h'] !== $hash_bit),就会报错,所以参数h的值必须要等于变量$hash_bit,其中上文提到,参数h是sha1(参数id+参数g+用户的密码md5怎么办?
先看一下数据库中用户1的password字段为8635fc开头
还是老样子,我们在本地环境跑一下,看看参数id+参数g+用户的密码md5算出来是多少
最终算出来为28160,为什么?因为在PHP中,整数和字符串进行运算,字符串若前面为字母,则取值为0,若字符串前面为数字,以这里密码md5的8635fc为例,则取字母前面的数字为值,所以说1(参数id的值)+19524(参数g的值)+8635(用户密码md5)=28160
但是我们也说了,参数h是sha1(参数id+参数g+用户的密码md5),计算出sha1后,再取其从第五个数开始的后十五位,所以得用脚本生成一个经过sha1和substr函数运算后的字典,再用burpsuite的intruder模块进行爆破(因为我们无法知道用户密码md5开头的值)
我这里是用PHP生成了一个字典,保存为h.txt
上面的字典,是爆破我们参数h的值
但是我们依然没有重置用户密码,还需要参数form_password_hidden和 form_change,所以还没有代码审计完
我们需要设置一个参数form_change,进入if判断,值为空就可以
我们的目的是进入if (!$msg->containsErrors()),里面可以执行update用户密码的SQL语句
关于!$msg->containsErrors(),要求前面参数id,g,h的值设置正确,就不会报错,ok,如果以上都理解了,我们就可以执行攻击了
参数form_password_hidden是重新设置的新密码,一般是md5,但是为了方便大家看懂,我这里的值直接设置为yyds,以显示我们攻击成功了,我们直接通过burpsuite的intrude模块进行爆破
我这里是成功了的,注意发包的时候,同个payload有时返回200有时候302,所以最好多尝试几次,响应的字段长度基本差不了多少,所以不是基于响应长度判断是否成功,我这里通过爆破成功修改了用户密码
反射型XSS
利用条件
l 版本为2.2.4
漏洞存在的位置在themes\default\include\header.tmpl.php
注意前面的if判断,页面中必须要定义了AT_INCLUDE_PATH常量,否则就exit退出,但是这个header.tmpl.php是不包含此常量的,也就是说它需要被其他文件包含(通过.tmpl.php的命名我们就可以知道这是一个模板文件)
我们来全局搜索下,哪些文件是包含了header.tmpl.php的,发现header.inc.php文件是包含的,但是header.inc.php又没有定义AT_INCLUDE_PATH常量,所以我们需要套娃,找到包含header.inc.php的文件
继续搜索,哪些文件是包含了header.inc.php的,发现about.php包含,而且定义了AT_INCLUDE_PATH常量,所以我们从about.php页面入手
Ok,找到要执行攻击的页面了,但是漏洞在哪里呢?
我们前文说过漏洞存在的位置在themes\default\include\header.tmpl.php
所以继续往下进行代码审计,一般反射型XSS肯定都是靠参数传递的啦,我们直接在该页面搜”$_”关键字,找到$_GET[‘fb’];
发现printNoLookupFeedback函数会将我们传的参数显示出来,这里有戏,但是我们要跟进函数,看看会不会过滤尖括号
这里又套娃了,刚刚的$msg->xx()是类中的一个函数,这里的$this->savant->xx()也是类中的函数,继续跟进吧
通过全局搜索可以知道,函数属于Savant2这个类,它是先把类Savant2的实例化(new Savant2 ())赋值给变量$ Savant,再将变量传参给类Message的实例化,即new Message($ Savant),类Message再通过构造函数,将传进来的值,赋给$this-> savant
看完了,这个传参点是没有过滤的,我们直接通过about.php页面执行XSS payload吧
储存型XSS
利用条件
l 获得管理员权限的用户
l 版本为2.2.4
先登录管理员用户,再打开mods/_core/users/admins/my_edit.php页面,在real_name位置提交XSS payload
保存后,打开mods/_core/users/admins/index.php页面,发现执行成功
漏洞出在mods/_core/users/admins/my_edit.php,我们对其进行审计
$msg->containsErrors()默认是false,所以负负得正,为true,进入if里面
其中,$addslashes会对我们提交的real_name值进行过滤,我们先看看$addslashes被赋了哪个过滤函数
$addslashes其实就是mysql_real_escape_string函数
过滤的无非是斜杠、单引号、双引号、回车这些,和尖括号无关,所以对XSS攻击是没有过滤的,除非你是通过标签的属性,即闭合双引号来XSS
变量覆盖
我的理解是,因为在安装WEB的时候,跳过了步骤5,即数据库配置这一块,导致安装的页面依然可以访问,二次安装,篡改数据库的配置,不过这个确实没想到,算是拓展下思路
6 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!