RaspAgent
public class RaspAgent {
public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException, IOException {
readVersion();
inst.addTransformer(new DefineTransformer(), true);
}
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException, IOException {
readVersion();
inst.addTransformer(new DefineTransformer(), true);
Class<?>[] classes = inst.getAllLoadedClasses();
for (Class<?> cls : classes){
if(cls.getName().equals(agentArgs)){
inst.retransformClasses(cls);
}
}
}
public static void readVersion() throws IOException {
Class clazz = RaspAgent.class;
String className = clazz.getSimpleName() + ".class";
String classPath = clazz.getResource(className).toString();
String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";
Manifest manifest = new Manifest(new URL(manifestPath).openStream());
Attributes attr = manifest.getMainAttributes();
String projectVersion = attr.getValue("Project-Version");
String buildTime = attr.getValue("Build-Time");
System.out.print("---- xxx-RASP ---- \n" +
"--- version:" + projectVersion + "---\n" +
"-- buildTime: "+ buildTime + "--\n");
}
static class DefineTransformer implements ClassFileTransformer{
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
Set<Class> hooks = AnnotationScanner.getClassWithAnnotation("com.zte.rasp", HookAnnotation.class);
for(Class hook: hooks){
try {
Object object = hook.newInstance();
if (object instanceof AbstractClassHook) {
AbstractClassHook item =(AbstractClassHook) object;
if(item.isClassMatched(className)){
CtClass clazz = null;
try {
final ClassPool classPool = ClassPool.getDefault();
clazz = classPool.get(className.replace("/","."));
return item.hookMethod(clazz, className);
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return classfileBuffer;
}
}
}
hook 流程
public class PreMainTraceAgent {
public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
System.out.println("agentArgs : " + agentArgs);
inst.addTransformer(new DefineTransformer(), true);
}
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("这是agentmain");
inst.addTransformer(new DefineTransformer(), true);
}
static class DefineTransformer implements ClassFileTransformer{
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(SQLPreparedStatementHook.isClassMatched(className)){
CtClass clazz = null;
try {
final ClassPool classPool = ClassPool.getDefault();
clazz = classPool.get(className.replace("/","."));
return SQLPreparedStatementHook.hookMethod(clazz, className);
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
}
}
if(XXEHook.isClassMatched(className)){
CtClass clazz = null;
try {
final ClassPool classPool = ClassPool.getDefault();
clazz = classPool.get(className.replace("/","."));
return XXEHook.hookMethod(clazz, className);
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return classfileBuffer;
}
}
}
Attach main文件
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.util.List;
public class AttachMain {
public static void main(String[] args) throws Exception {
// VirtualMachine vm = VirtualMachine.attach(args[0]);
//System.out.print(args[0]);
List<VirtualMachineDescriptor> list = VirtualMachine.list();
String pid = "";
for (VirtualMachineDescriptor vmd : list)
{
if(vmd.displayName().equals(args[0])){
pid = vmd.id();
break;
}
}
VirtualMachine vm = VirtualMachine.attach(pid);
try {
vm.loadAgent(args[1], args[0]);
} finally {
vm.detach();
}
}
}
Annotation代码
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HookAnnotation {
}
Annotation Scanner代码
import java.io.File;
import java.io.FileFilter;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @description: 扫描特定包名下的含有特定的注解的类
* @author: anyang
* @create: 2018/08/29 21:04
*/
public class AnnotationScanner {
public static Set<Class> getClassWithAnnotation(String packageName, Class annotationClass) {
Set<Class> classes = new LinkedHashSet<Class>();
Set<Class> res = new LinkedHashSet<Class>();
String newPackageName = packageName.replace('.', '/');
Enumeration<URL> urls;
try {
urls = AnnotationScanner.class.getClassLoader().getResources(newPackageName);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
findAndAddClass(packageName, filePath, classes);
} else if ("jar".equals(protocol)) {
JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
if (jar != null) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.startsWith("/")) {
name = name.substring(1);
}
if (name.startsWith(newPackageName)) {
int index = name.lastIndexOf('/');
if ((index != -1) && name.endsWith(".class") && !entry.isDirectory()) {
packageName = name.substring(0, index).replace('/', '.');
String className = name.substring(packageName.length() + 1, name.length() - 6);
classes.add(Class.forName(packageName + '.' + className));
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
for (Class clazz : classes) {
if (clazz.getAnnotation(annotationClass) != null) {
res.add(clazz);
}
}
return res;
}
private static void findAndAddClass(String packageName, String packagePath, Set<Class> classes) {
File baseDir = new File(packagePath);
if (!baseDir.exists() || !baseDir.isDirectory()) {
return;
}
File[] fileList = baseDir.listFiles(new FileFilter() {
public boolean accept(File file) {
return (file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
if (fileList != null && fileList.length > 0) {
for (File file : fileList) {
if (file.isDirectory()) {
findAndAddClass(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
} else {
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
classes.add(AnnotationScanner.class.getClassLoader().loadClass(packageName + '.' + className));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
抽象hook类
public abstract class AbstractClassHook {
public abstract Boolean isClassMatched(String className);
public abstract byte[] hookMethod(CtClass clazz, String className) throws CannotCompileException, NotFoundException, IOException;
}
检测sql注入的具体hook类
@HookAnnotation
public class SQLPreparedStatementHook extends AbstractClassHook {
private String className;
//类匹配函数
public Boolean isClassMatched(String className){
if(className.equals("org/hsqldb/jdbc/JDBCStatement")||
className.equals("com/alibaba/druid/pool/DruidPooledPreparedStatement")
){
System.out.print("加载到SQL-prepared-JDBC类");
return true;
}
return false;
}
//字节码修改方法注入
public byte[] hookMethod(CtClass clazz, String className) throws CannotCompileException, NotFoundException, IOException {
clazz.addMethod(CtMethod.make(getIsValidSqlOrigCode(), clazz));
String originSql = getOriginalSql(className);
String hookCode = originSql + "if(isValSql(sql)==1){System.out.print(\"有效sql\");}else{System.out.print(\"可能有sql注入\");}";
CtMethod ctMethod_query = clazz.getDeclaredMethod("executeQuery");
CtMethod ctMethod_execute = clazz.getDeclaredMethod("execute");
CtMethod ctMethod_update = clazz.getDeclaredMethod("executeUpdate");
ctMethod_query.insertBefore(hookCode);
ctMethod_execute.insertBefore(hookCode);
ctMethod_update.insertBefore(hookCode);
byte[] byteCode = clazz.toBytecode();
clazz.detach();
return byteCode;
}
//获取各类数据库的执行sql参数
public static String getOriginalSql(String className){
String originalSql = "";
if (className.equals("org/hsqldb/jdbc/JDBCStatement")){
originalSql = "String sql = $1;";
}
return originalSql;
}
public static String getIsValidSqlOrigCode(){
String hookCode = "public static int isValSql(String sql){\n" +
" System.out.println(sql);" +
" if(sql.contains(\" or \")){" +
" String[] sql_list = sql.split(\" or \");" +
" String sqlChar = sql_list[1];" +
" String[] sql_char_list = sqlChar.split(\"=\");" +
" String sqlChar0 = sql_char_list[0].replace(\" \",\"\");" +
" String sqlChar1 = sql_char_list[1].replace(\" \",\"\").replace(\";\",\"\");" +
" if(sqlChar0.equals(sqlChar1)){" +
" System.out.println(\"同义反复\");" +
" return 0; } " +
" }" +
" for(int i=0; i<sql.length()-1; i++){ " +
" if(sql.charAt(i)==\';\'){" +
" System.out.println(\"联合查询\");" +
" return 0;}" +
" if(sql.charAt(i)==\'0\' && sql.charAt(i+1)==\'x\'){" +
" System.out.println(\"检测到16进制值\");" +
" return 0;}" +
" }"+
" try {\n" +
" net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(sql);\n" +
" return 1;\n" +
" } catch (net.sf.jsqlparser.JSQLParserException e) {\n" +
" System.out.println(\"SQL语法解析错误\");" +
" return 0;\n" +
" } catch (Throwable t) {\n" +
" t.printStackTrace();\n" +
" return 0;\n" +
" }\n" +
" }";
return hookCode;
}
}
依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
相关内容:
JSqlParser是一个SQL语句解析器。它将SQL转换为Java类的可遍历层次结构。
支持Oracle,SqlServer,MySQL,PostgreSQL等常用数据库。但各种数据库系统的SQL语法都在动态变化,可以解析某些(不是全部)。
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>3.2</version>
</dependency>
Select select = (Select) CCJSqlParserUtil.parse("SELECT username,age,sex FROM user or 1=1");
SelectBody selectBody = select.getSelectBody();
System.out.print(select);
System.err.println(selectBody);
sql注入案例语句:
select department from employees where first_name = 'Bob'
select department from employees where first_name = 'Bob' and;
select department from employees where first_name = 'Bob' or 1 = 1;
select department from employees where first_name = 'Bob'; select * from employees;
select department from employees where first_name = '0x'