0x1 SQL 注入 CVE-2022-37202
描述
/admin/advicefeedback/list 参数处理不当,可被利用执行恶意SQL命令。
代码
java
public SQLUtils(String sql) {
sqlBuffer = new StringBuffer(sql);
}
tb_advice_feedback表下查询

java
@ControllerBind(controllerKey = "/admin/advicefeedback")
public class AdvicefeedbackController extends BaseProjectController {
private static final String path = "/pages/admin/advicefeedback/advicefeedback_";
public void list() {
//这里自动获取
TbAdviceFeedback model = getModelByAttr(TbAdviceFeedback.class);
//查询
SQLUtils sql = new SQLUtils(" from tb_advice_feedback t where 1=1 ");
//某个条件
if (model.getAttrValues().length != 0) {
//拼接t,存储进alias
sql.setAlias("t");
// 查询条件,model.获取,代入
sql.whereLike("username", model.getStr("username"));
sql.whereLike("qq", model.getStr("qq"));
sql.whereLike("email", model.getStr("email"));
sql.whereLike("telphone", model.getStr("telphone"));
}
// 排序
//怎么获取
String orderBy = getBaseForm().getOrderBy();
//sql
if (StrUtils.isEmpty(orderBy)) {
//尾部添加
sql.append(" order by t.id desc ");
} else {
//拼接
sql.append(" order by ").append(orderBy);
}
Page<TbAdviceFeedback> page = TbAdviceFeedback.dao.paginate(getPaginator(), "select t.* ", //
sql.toString().toString());
// 下拉框
setAttr("page", page);
setAttr("attr", model);
render(path + "list.html");
}
String orderBy = getBaseForm().getOrderBy()
java
public BaseForm getBaseForm() {
BaseForm form = super.getAttr("form");
return form == null ? new BaseForm() : form;
}
java
public String getOrderBy() {
if (StrUtils.isEmpty(getOrderColumn())) {
return "";
}
//返回字符串
return " " + getOrderColumn() + " " + getOrderAsc() + " ";
}
whereLike sql
java
private String alias = "";
public void setAlias(String alias) {
this.alias = alias;
}
java
//键,值
public void whereLike(String attrName, String value) {
//过滤
if (checkSQLInject(attrName) || checkSQLInject(value)) {
//返回正常字段
return;
}
//代入查询,值
if (StrUtils.isNotEmpty(value)) {
//t拼接attrName
//append(value)可控
//模糊匹配value
sqlBuffer.append(" AND " + getAttrName(attrName) + " LIKE '%").append(value).append("%'");
}
}
java
private String getAttrName(String attrName) {
if (StrUtils.isEmpty(alias)) {
//返回attrName
return attrName;
}
//拼接
return alias + "." + attrName;
}
java
//sql.setAlias("t");
private String alias = "";
java
//建过滤
public static boolean checkSQLInject(String str) {
// 如果传入空串则认为不存在非法字符
if (StrUtils.isEmpty(str)) {
return false;
}
// 判断黑名单
String[] blacks = {"script", "mid", "master", "truncate", "insert", "select", "delete", "update", "declare",
"iframe", "'", "onreadystatechange", "alert", "atestu", "xss", ";", "'", "<", ">", "(", ")",
// ",",, "\""
"\\", "svg", "confirm", "prompt", "onload", "onmouseover", "onfocus", "onerror"};
// 判断白名单
String[] whites = {"updatetime", "update_time", "\""};
// sql不区分大小写
str = str.toLowerCase();
//2选1
//完整判断
for (int i = 0; i < whites.length; i++) {
if (whites[i].equals(str)) {
return false;
}
}
//黑名单
for (int i = 0; i < blacks.length; i++) {
//字符判断
if (str.indexOf(blacks[i]) >= 0) {
//日志
logger.error("SQLInject 原因:特殊字符,传入str=" + str + ",包含特殊字符:" + blacks[i]);
//检查到sql
return true;
}
}
return false;
}
}
java
//值过滤
public static boolean checkSQLInject(String str) {
// 如果传入空串则认为不存在非法字符
if (StrUtils.isEmpty(str)) {
return false;
}
// 判断黑名单
//url绕过(post排除),/**/绕过,不安全过滤
String[] blacks = {"script", "mid", "master", "truncate", "insert", "select", "delete", "update", "declare",
"iframe", "'", "onreadystatechange", "alert", "atestu", "xss", ";", "'", "<", ">", "(", ")",
// ",",, "\""
"\\", "svg", "confirm", "prompt", "onload", "onmouseover", "onfocus", "onerror"};
// 判断白名单
String[] whites = {"updatetime", "update_time", "\""};
// sql不区分大小写
str = str.toLowerCase();
//判断是不是白名单,必须有字符
for (int i = 0; i < whites.length; i++) {
if (whites[i].equals(str)) {
return false;
}
}
//报错
for (int i = 0; i < blacks.length; i++) {
if (str.indexOf(blacks[i]) >= 0) {
logger.error("SQLInject 原因:特殊字符,传入str=" + str + ",包含特殊字符:" + blacks[i]);
return true;
}
}
return false;
}
}
java
public String getStr(String attr) {
// return (String)attrs.get(attr);
Object s = attrs.get(attr);
return s != null ? s.toString() : null;
}
java
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
poc
其实思路很明显了,闭合',并且绕过过滤
为什么闭合单引号
sqlBuffer.append(" AND " + getAttrName(attrName) + " LIKE '%").append(value).append("%'")
实际拼接
AND name LIKE '%abc%'
重点
sqlBuffer.append(" AND " + getAttrName(attrName) + " LIKE '%").append(value).append("%'");
绕过
{"script", "mid", "master", "truncate", "insert", "select", "delete", "update", "declare",
"iframe", "'", "onreadystatechange", "alert", "atestu", "xss", ";", "'", "<", ">", "(", ")",
// ",",, "\""
"\\", "svg", "confirm", "prompt", "onload", "onmouseover", "onfocus", "onerror"};
验证
如果拼接过后是%%%,就会全部展现出来。

复现
这个功能,查询功能


刷新发现有默认
子页面保存,一次注入