> 文章列表 > 代码审计实战3-android java

代码审计实战3-android java

代码审计实战3-android  java

jks

java keystore

作用:保证应用的唯一性

简介:可以理解为java的密钥库,是一个用来存放密钥和证书的仓库。
(而keytool就是密钥和证书的管理工具,它把key(密钥)和certificate(证书)存放在一个叫keystore文件中)

jks可以同时容纳n个公钥或私钥,后缀一般是.jks或者.keystore或.truststore等,千奇百怪
用jdk\\bin目录下的keytool.exe对其进行查看,导入,导出,删除,修改密码等各种操作。

使用举例:Android Studio生成.jks文件

代码审计实战3-android  java
使用举例:生成jks证书文件:确保安装了JDK,在命令行中输入如下命令

keytool.exe -genkeypair -alias filename -keyalg RSA -keypass 501937 -storepass 501937 -keyalg RSA -keysize 2048 -validity 3650 -keystore filename.jks
您的名字与姓氏是什么?
[Unknown]:  cheng
您的组织单位名称是什么?
[Unknown]:  wang
您的组织名称是什么?
[Unknown]:  qiu
您所在的城市或区域名称是什么?
[Unknown]:  shanghai
您所在的省//自治区名称是什么?
[Unknown]:  shanghai
该单位的双字母国家/地区代码是什么?
[Unknown]:  CN
CN=cheng, OU=wang, O=qiu, L=shanghai, ST=shanghai, C=CN是否正确?
[]: yWarning:                                        
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore filename.jks -destkeystore filename.jks -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。keytool -importkeystore -srckeystore filename.jks -destkeystore filename.jks -deststoretype pkcs12
输入源密钥库口令:
已成功导入别名 filename 的条目。
已完成导入命令: 1 个条目成功导入, 0 个条目失败或取消Warning:
已将 "filename.jks" 迁移到 Non JKS/JCEKS。将 JKS 密钥库作为 "filename.jks.old" 进行了备份。注:
storepass keystore 文件存储密码
keypass 私钥加解密密码
PS: 上面两个密码要一致
keyalt 采用公钥算法,默认是DSA
validity 有效期 单位是天
keysize 密钥长度(DSA算法对应的默认算法是sha1withDSA,不支持2048长度,此时需指定RSA)
keystore 指定keystore文件 如上面命令中filename.jks

签名文件kestore和jks:
在作用上基本上没有太大区别,主要是生成来源不一样,它们是由不同的IDE生成,
keystore文件一般是由Eclipce或dos命令行生成,而jks一般是在Android studio上自动生成。

要实现这个两种签名文件的相互转化,需要用到一个工具:keytool。
代码审计实战3-android  java

注意到这个知识点是因为,我在build.gradle文件中看到

signingConfigs {release {storeFile file("../myapp.jks")storePassword"mypassword"keyAlias"My_App"keyPassword"mykeypassword"}debug {storeFile file('../debug.keystore')storePassword 'android'keyAlias 'androiddebugkey'keyPassword 'android'}}

类似这种,我以为是硬编码

关于编码:保护Android Java代码中的硬编码值(API密钥):https://www.codenong.com/35378295/

但其实将密钥添加到build.gradle文件中。 这些文件不包含在您的apk中。 实际上,这些只是用来对您的应用程序进行签名并创建发行版本。

不算硬编码。
那可以算弱口令吗?

APP客户端与后台服务端通信

https://www.bbsmax.com/A/x9J2kbXZ56/

AIDL(Android Interface definition language-“接口定义语言”) 是 Android 提供的一种进程间通信 (IPC:Inter-Process Communication) 机制,支持的数据类型:

  1. Java 的原生类型;
  2. String 和CharSequence;
  3. List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型; 以上三种类型都不需要导入(import);
  4. AIDL 自动生成的接口 需要导入(import);
  5. 实现android.os.Parcelable 接口的类. 需要导入(import)。

加密

加密算法:https://www.pianshen.com/article/4442439864/
https://blog.csdn.net/xihuailu3244/article/details/109769037
https://www.cnblogs.com/whoislcj/p/5580950.html
http://www.360doc.com/content/23/0210/12/81615618_1067007071.shtml
密钥常量:https://www.jianshu.com/p/f366b7c115a5
为什么我们应该编写所有这些步骤来使用java生成DES算法的密钥https://www.orcode.com/question/1320303_k56849.html
https://blog.csdn.net/mlymark/article/details/49175789
https://www.orcode.com/question/1320303_k56849.html
Android应用安全开发之浅谈硬编码: https://www.pianshen.com/article/9026439286/
https://blog.csdn.net/mlymark/article/details/49175789

Intent Scheme URLs攻击

Environment.getExternalStorageDirectory()

Android SDK 版本超过29编译的时候,Android Studio会提示Environment.getExternalStorageDirectory()过时了,要用Context#getExternalFilesDir代替,

Android Q以后Environment.getExternalStorageDirectory()返回的路径可能无法直接访问,所以改成了Context#getExternalFilesDir

android:exported=“true”

android:exported 是描述四大组件的参数,表示当前组件能否被其他应用程序的组件调用或与之交互。

android:exported 为true,表示可以和其他应用程序的组件发生调用和交互。
android:exported 为false,表示只能当前应用程序组件或者带有相同的用户ID(shareUserId)的应用程序调用和交互。

exported属性的默认值四大组件略有不同:

  • Activity/Service/Receiver:当没有设置 intent-filter时 ,默认false。
  • Provider:当Android sdk版本为16或更低版本时,默认值为true,如果是17及以上版本则默认为false。

设置intent-filter的场景

        <serviceandroid:name="com.alibaba.sdk.android.push.MsgService"><intent-filter><action android:name="com.alibaba.sdk.android.push.NOTIFY_ACTION" /></intent-filter></service>

此情况下说明,除了“内部使用”外,组件需要被部分“特定”App调用的,推荐
1、调用的App与当前暴露组件的App使用同一uid:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"...android:sharedUserId="xxx.xxx.xxx">

2、或通过对暴露的组件设置permission:

<activity android:name=".xxxActivity"android:label="自定义permission"android:permission="com.xxx.permission" ><intent-filter><action android:name="android.xxx.action" /><category android:name="android.intent.category.DEFAULT" /></intent-filter>
</activity>

既可满足暴露组件的需求,又可以保护数据安全。

https://blog.csdn.net/weixin_35691921/article/details/120430987

Intent(意图)

负责完成Android应用、组件之间的交互与通信。
常见的Activity的调用、Receiver的发送、Service的启动都需离不开Intent。

Intent通常包含的信息:
Categpry:种类、归类。
Action:表明要做什么?通常代表了一个组件具有的能力。
Data/Extras:通信的数据。
Flags:规定了系统如何去启动一个Activity。

Activity(活动)

通常应用里一个显示界面就是一个Activity,Activity负责显示界面的元素和与用户之间的交互。

https://blog.csdn.net/wsq_tomato/article/details/83349812
https://blog.csdn.net/qy1387/article/details/73740438
https://zhuanlan.zhihu.com/p/554069141

WebView

https://blog.csdn.net/qq_35114086/article/details/88796144/
https://blog.csdn.net/weixin_35691921/article/details/120430987
https://code84.com/750978.html
https://www.jianshu.com/p/6e9695fdedca

Android WebView在Android平台上是一个特殊的View,它能用来显示网页,这个WebView类可以被用来在app中仅仅显示一张在线的网页,还可以用来开发浏览器。
WebView内部实现是采用渲染引擎(WebKit)来展示view的内容,提供网页前进后退、网页放大、缩小、搜索等功能。Android WebView 在低版本和高版本采用了不同的 webkit 版本内核,在 4.4 版本后使用 Chrome 内核。

作用:

  • 显示和渲染Web页面
  • 直接使用html文件(网络上或本地assets中)作布局
  • 可和JavaScript交互调用

WebView控件功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载、渲染、页面交互进行强大的处理。

使用:

1、本地加载
在布局文件中来添加WebView控件
代码审计实战3-android  java
在代码中让WebView控件加载显示网页
代码审计实战3-android  java
在配置文件中添加网络权限:

<!-- 添加网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />

运行效果
代码审计实战3-android  java
2、远程加载
在本地桌面新建js文件attack.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Carson</title><script>function callAndroid(){//由于对象映射,所以调用test对象等于调用Android映射的对象test.hello("WindXaa!");}
</script>
</head>
<body><!--点击按钮则调用callAndroid函数--><button type="button" id="button1" onclick="callAndroid()">Internet Click connect</button>
</body>
</html>

开启一个简易的http_server的监听
代码审计实战3-android  java
编写代码:

{
WebView mWebView = (WebView) findViewById(R.id.Wind_webview1);
WebSettings webSettings = mWebView.getSettings();// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);// 通过addJavascriptInterface()将Java对象映射到JS对象
//参数1:Javascript对象名
//参数2:Java对象名
mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象
mWebView.loadData("","text/html",null);
// 加载JS代码
// 格式规定为:file:///android_asset/文件名.html
// mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.loadUrl("http://ip地址填自己的/attack.html");
}/*** 提供接口在Webview中供JS调用*/
public class AndroidtoJs {// 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解@JavascriptInterfacepublic void hello(String msg) {Log.e("WindXaa","Hello," + msg);}
}

运行效果:
点击按钮,成功的通过JS调用Android代码
代码审计实战3-android  java
WebView常用方法

webView.onResume();// 激活WebView为活跃状态,能正常执行网页的响应
webView.onPause();// 当页面被失去焦点被切换到后台不可见状态,需要执行onPause
// 通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
webView.pauseTimers()// 当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview
// 它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。
webView.resumeTimers()// 恢复pauseTimers状态rootLayout.removeView(webView)
webView.destory()
// webview调用destory时,webview仍绑定在Activity上
// 需要先从父容器中移除webview,然后再销毁webview前进、后退网页Webview.canGoBack()//是否可以后退
Webview.goBack()//后退网页
Webview.canGoForward()//是否可以前进
Webview.goForward()//前进网页
//以当前的index为起始点前进或者后退到历史记录中指定的steps
//如果steps为负数则为后退,正数则为前进
Webview.goBackOrForward(intsteps)在不做任何处理前提下,浏览网页时点击系统的“Back”键时,整个 Browser 会调用 finish()而结束自身,因此需要在当前Activity中处理并消费掉该 Back 事件,当按下返回键时,调用goBack方法。
我们可以做一些处理,让点击“Back”键后,让网页返回上一页而不是直接退出浏览器,此时我们可以在当前的Activity中处理Back事件,如下:
public boolean onKeyDown(int keyCode, KeyEvent event) {if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {mWebView.goBack();return true;}return super.onKeyDown(keyCode, event);}

常用类

WebSettings类:对WebView进行配置和管理配置步骤:
第一步:添加访问网络权限(AndroidManifest.xml)
<uses-permission android:name="android.permission.INTERNET"/>注意:从Android 9.0(API级别28)开始,默认情况下禁用明文支持,会显示 ERR_CLEARTEXT_NOT_PERMITTED。
因此http的url均无法在webview中加载,可以在manifest中application节点添加android:usesCleartextTraffic="true"。第二步:生成一个WebView组件(有两种方式)
//方式1:直接在在Activity中生成
WebView webView = new WebView(this)
//方法2:在Activity的layout文件里添加webview控件:
WebView webview = (WebView) findViewById(R.id.webView1);第三步:进行配置-利用WebSettings子类(常见方法)
WebSettings webSettings = webView.getSettings();//声明WebSettings子类
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
//支持插件
webSettings.setPluginsEnabled(true);//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小//缩放操作
webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式常见方法:设置WebView缓存当加载 html 页面时,WebView会在/data/data/包名目录下生成 database 与 cache 两个文件夹
请求的 URL记录保存在 WebViewCache.db,而 URL的内容是保存在 WebViewCache 文件夹下
是否启用缓存:
//优先使用缓存
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//缓存模式如下://LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据//不使用缓存
WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
WebViewClient类:用来处理各种通知和请求事件shouldOverrideUrlLoading():打开网页时不调用系统浏览器, 而是在本WebView中显示;在网页上的所有加载都经过这个方法,这个函数我们可以做很多操作。//Webview控件
Webview webview = (WebView) findViewById(R.id.webView);
//加载一个网页
webView.loadUrl("http://www.google.com/");
//重写shouldOverrideUrlLoading()方法,使得打开网页时不调用系统浏览器, 而是在本WebView中显示
webView.setWebViewClient(new WebViewClient(){@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {view.loadUrl(url);return true;}});onPageStarted()作用:开始载入页面调用的,我们可以设定一个loading的页面,告诉用户程序在等待网络响应。webView.setWebViewClient(new WebViewClient(){@Overridepublic void  onPageStarted(WebView view, String url, Bitmap favicon) {//设定加载开始的操作}});onLoadResource()作用:在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。webView.setWebViewClient(new WebViewClient(){@Overridepublic boolean onLoadResource(WebView view, String url) {//设定加载资源的操作}});onReceivedError()作用:加载页面的服务器出现错误时(如404)调用。//App里面使用webview控件的时候遇到了诸如404这类的错误的时候,若也显示浏览器里面的那种错误提示页面就显得很丑陋了,那么这个时候我们的app就需要加载一个本地的错误提示页面,即webview如何加载一个本地的页面。//步骤1:写一个html文件(error_handle.html),用于出错时展示给用户看的提示页面
//步骤2:将该html文件放置到代码根目录的assets文件夹下
//步骤3:复写WebViewClient的onRecievedError方法
//该方法传回了错误码,根据错误类型可以进行不同的错误分类处理webView.setWebViewClient(new WebViewClient(){@Overridepublic void onReceivedError(WebView view, int errorCode, String description, String failingUrl){
switch(errorCode){case HttpStatus.SC_NOT_FOUND:view.loadUrl("file:///android_assets/error_handle.html");break;}}});onReceivedSslError():处理https请求
webView默认是不处理https请求的,页面显示空白,需要进行如下设置:webView.setWebViewClient(new WebViewClient() {   @Override   public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {   handler.proceed();    //表示等待证书响应// handler.cancel();      //表示挂起连接,为默认方式// handler.handleMessage(null);    //可做其他处理}   });c.WebChromeClient:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等。onProgressChanged():获得网页的加载进度并显示webview.setWebChromeClient(new WebChromeClient(){@Overridepublic void onProgressChanged(WebView view, int newProgress) {if (newProgress < 100) {String progress = newProgress + "%";progress.setText(progress);} else {}});onReceivedTitle():获取Web页中的标题每个网页的页面都有一个标题,比如http://www.baidu.com这个页面的标题即“百度一下,你就知道”,那么如何知道当前webview正在加载的页面的title并进行设置呢?webview.setWebChromeClient(new WebChromeClient(){@Overridepublic void onReceivedTitle(WebView view, String title) {titleview.setText(title)}

Android WebView与JS的交互

https://mp.weixin.qq.com/s/KcmhXCIE8cr32OVBVkc4Lg

代码审计实战3-android  java

安全风险

webview.getSettings().setJavaScriptEnabled(true)
设置WebView是否允许执行JavaScript脚本,默认false。

当targetSdkVersion小于17时,攻击者可以利用addJavascriptInterface这个接口添加的函数远程执行任意代码

解决办法:

  • API17 (Android 4.2) 版本之后,需要在被调用的地方加上 @addJavascriptInterface 约束注解,因为不加上注解的方法是没有办法被调用的。进而避免漏洞攻击。
  • API < 17:需要采用拦截prompt()的方式进行漏洞修复。

https://blog.csdn.net/weixin_32534669/article/details/117833703

https

https://blog.51cto.com/u_4029519/5425558
https://blog.csdn.net/caizehui/article/details/112464577
https://mp.weixin.qq.com/s?__biz=MzIyNTgwOTIyOQ==&mid=2247489201&idx=1&sn=3a93ec381c40e7497bed2ad5196bcf10&chksm=e87b5093df0cd9858866724530f94427a653f1921b951fac36104352764f9d912b079e289a3d&scene=27

存储

https://blog.csdn.net/libo407/article/details/129862032?ydreferer=aHR0cHM6Ly93d3cuYmFpZHUuY29tL2xpbms%2FdXJsPTYzU2RQOU03ejRSS0xWSWpueE5NY185RS1WZzFmMUlQUExfQWUzUVBiUFBNQU9FOTBkSDFERGN3Qkp3bWduaHlaa1B6S1NITHpqcFN4d1R3Q2pKZlhxNHByTDFEbUlZN1JYWUczaTZrcHZPJndkPSZlcWlkPWFiNjhjZmM0MDAwMDQxNDkwMDAwMDAwNDY0MzY1YTYz
https://blog.csdn.net/qq_20466945/article/details/80199252
https://blog.csdn.net/Ananas_Orangey/article/details/126219957
https://www.jb51.net/article/220703.htm

审计参考

https://blog.51cto.com/u_14397532/3000922
https://mp.weixin.qq.com/s?__biz=MzIyNTgwOTIyOQ==&mid=2247489201&idx=1&sn=3a93ec381c40e7497bed2ad5196bcf10&chksm=e87b5093df0cd9858866724530f94427a653f1921b951fac36104352764f9d912b079e289a3d&scene=27
https://www.wangan.com/p/7fy7f882627f2fdb#8.1谨慎使用高风险函数

代码审计2-java

@PreAuthorize

@PreAuthorize("@ss.hasPermi('show:type:list')"):用于权限控制,只有拥有’show:type:list’权限的用户才能访问这个接口

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

作用在代码的注解是:
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。作用在其他注解的注解(或者说 元注解):
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 成员。
@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

Annotation通用定义

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}

(1)@interface
定义 Annotation 时,@interface 是必须的。
使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
(2)@Documented
类和方法的 Annotation 在缺省情况下是不出现在 javadoc 中的。如果使用 @Documented 修饰该 Annotation,则表示它可以出现在 javadoc 中。

定义 Annotation 时,@Documented 可有可无;若没有定义,则 Annotation 不会出现在 javadoc 中。

(3)@Target(ElementType.TYPE)
ElementType 是 Annotation 的类型属性。而 @Target 的作用,就是来指定 Annotation 的类型属性。

ElementType.METHOD:该注解只能声明在一个类的方法前。
ElementType.TYPE:该注解只能声明在一个类前。
(4)@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是 RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息保留在 .class 文件中,并且能被虚拟机读取。

总结: @interface 用来声明 Annotation,@Documented 用来表示该 Annotation 是否会出现在 javadoc 中, @Target 用来指定 Annotation 的类型,@Retention 用来指定 Annotation 的策略。

Spring Security

Spring Security提供了Spring EL表达式,允许我们在定义接口访问的方法上面添加注解,来控制访问权限。

@PreAuthorize注解用于配置接口,要求用户拥有某些权限才可访问,它拥有如下方法
代码审计实战3-android  java
1、数据权限
整体逻辑:每个用户分配角色之后,都会有一定的权限,在用户访问某个接口时,会判断用户所拥有的权限是否包含该接口需要的权限,然后将判断的结果(“true"或者"fales”)传入@PreAuthorize注解实现数据权限的控制。当Spring EL 表达式返回TRUE,则权限校验通过。

示例:
代码审计实战3-android  java
@PreAuthorize注解括号里面接收的是String类型的参数,且值为 “true” 或者 “false”

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {String value();}

ss.hasPermi() 方法:
ss:ss是PermissionService类实例化后起的的对象名

hasPermi() 方法接收一个 String类型的参数,值为’system:user:add’,返回值为布尔类型,首先进行校验,判断参数是否为空,然后拿到当前登录系统的用户,判断当前用户是否为空以及用户拥有的权限是否为空(调用 loginUser.getPermissions() 方法,返回一个Set集合的权限列表),最后调用 hasPermissions(loginUser.getPermissions(), permission) 方法判断用户是否有该权限,如果包含就返回 true。
代码审计实战3-android  java
代码审计实战3-android  java
2、角色权限
角色权限时根据用户所属的角色来进行权限控制,实际原理和数据权限一样。

// 属于user角色
@PreAuthorize("@ss.hasRole('user')")// 不属于user角色
@PreAuthorize("@ss.lacksRole('user')")// 属于user或者admin之一
@PreAuthorize("@ss.hasAnyRoles('user,admin')")

@Validated

@Valid和@Validated是Spring Validation框架提供的参数验证功能

二者都作为标准JSR-303规范。在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:

@Valid:@Valid注解用于校验,所属包为:javax.validation.Valid。
用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

@Validated:@Validated是@Valid 的一次封装,是Spring提供的校验机制使用。
用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

-----引入并使用@Validated参数验证:

1、引入校验的依赖包

        <!--第一种方式导入校验依赖:使用springboot时,在org\\springframework\\spring-context\\5.2.1.RELEASE\\spring-context-5.2.1.RELEASE.jar--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--第二种方式导入校验依赖--><dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version></dependency><!--第三种方式导入校验依赖--><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId></dependency>

2、在实体类定义要校验的参数
代码审计实战3-android  java
3、代码审计实战3-android  java

javax.validation.constraints下参数条件注解详解

实现参数验证功能,我们需要@Validated注解配合 在实体类的的参数加上条件验证注解(设置具体的条件限制规则)一起实现参数验证功能。

而这些参数条件注解是由javax.validation.constraints包下提供,主要如下:

@NotNull :被注解的元素必须不为null
@NotBlank注解 :验证注解的元素值不为空(不为null、去除首位空格后长度为0) ,并且类型为String@NotEmpty注解 :验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) ,并且类型为String@AssertTrue注解 :被注解的元素必须为true,并且类型为boolean@AssertFalse注解 :被注解的元素必须为false,并且类型为boolean@Min注解 :被注解的元素其值必须大于等于最小值,并且类型为intlongfloatdouble@Max注解:被注解的元素其值必须小于等于最小值,并且类型为intlongfloatdouble@DecimalMin注解 :验证注解的元素值大于等于@DecimalMin指定的value值,并且类型为BigDecimal@DecimalMax注解 :验证注解的元素值小于等于@DecimalMax指定的value值 ,并且类型为BigDecimal@Range注解 :验证注解的元素值在最小值和最大值之间,并且类型为BigDecimalBigIntegerCharSequencebyteshortintlong@Past注解 :被注解的元素必须为过去的一个时间,并且类型为java.util.Date@Future注解 :被注解的元素必须为未来的一个时间,并且类型为java.util.Date@Size注解 :被注解的元素的长度必须在指定范围内,并且类型为StringArrayListMap@Length注解 :验证注解的元素值长度在min和max区间内 ,并且类型为String@Digits注解 :验证注解的元素值的整数位数和小数位数上限 ,并且类型为floatdoubleBigDecimal@Pattern注解 :被注解的元素必须符合指定的正则表达式,并且类型为String@Email注解: 验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式,类型为String

自定义条件注解

如果在写项目的过程中,参数需要的条件注解满足不上,则我们需要自定义注解来完成

步骤:
1.创建一个自定义的注解类

//自定义条件注解
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
public @interface ListValue {//配置路径,后端传递信息String message() default "{com.itfuture.e.valid.ListValue.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};//自定义一个类型来存放数据(数组)int[] values() default {};
}

String message() default "{com.itfuture.e.valid.ListValue.message}";也可以通过配置文件去配置:
代码审计实战3-android  java

2、创建一个逻辑处理数据的方法

//自定义显示状态
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {//set存储private Set<Integer> set = new HashSet<>();//初始化数据//listValue拿到的是注解中的数据@Overridepublic void initialize(ListValue constraintAnnotation) {//拿到注解中自定义的数据,且是数组型的int[] values = constraintAnnotation.values();//放在数组里,遍历判断for(int value:values){set.add(value);}}//判断数据是否相同@Overridepublic boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {if(set.contains(integer)){return true;}return false;}
}

3、在实体类的参数条件中来调用

代码审计实战3-android  java

spring-boot-devtools热部署

  • 是一个为开发者服务的模块,可以实现 Spring Boot 热部署,其中最重要的功能就是自动将应用代码更改到最新的 App 上面去。
  • 可以实现页面热部署,实现类文件热部署(类文件修改后不会立即生效),实现对属性配置文件的热部署。其原理是 spring-boot-devtools 会监听 Classpath 下的文件变动,并且会立即重启应用(发生在保存时机)。
  • 由于采用的虚拟机机制,重启的时候只是加载了在开发的 Class,没有重新加载第三方的 JAR 包,所以重启是很快的。

其深层原理是使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为 restart ClassLoader ,这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间

idea使用示例:
由于idea 没有保存修改,也就是说在idea中并不会因为ctrl+s 就重新编译代码。那么就需要额外的配置

1、在pom文件中,增加编译插件,让代码有变动的时候也编译

<!--热部署依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!--没有该项配置,热部署不会起作用--><configuration><fork>true</fork></configuration></plugin>

2、application.properties

# 页面修改后立即生效,关闭缓存,立即刷新
spring.thymeleaf.cache=false
# 热部署生效
spring.devtools.restart.enabled=true
# 设置需要重启的目录
spring.devtools.restart.additional-paths=src/main/java
# 设置不需要重启的目录
spring.devtools.restart.exclude=static/**,public/**,WEB-INF/**
# 为 mybatis 设置,生产环境可删除
# restart.include.mapper=/mapper-[\\\\w-\\\\.]+jar
# restart.include.pagehelper=/pagehelper-[\\\\w-\\\\.]+jar

3、设置 IDEA 环境自动编译
在编译器选项中勾选 Build project automatically 选项
代码审计实战3-android  java
代码审计实战3-android  java

FreeMarker模板注入

https://zhuanlan.zhihu.com/p/432361789

FreeMarker 是一款模板引擎,即一种基于模板和需要改变的数据, 并用来生成输出文本( HTML 网页,电子邮件,配置文件,源代码等)的通用工具,其模板语言为 FreeMarker Template Language (FTL),模板文件简称为FTL(后缀可能也为这个)。

它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件,适用于Web开发框架生成html页面。所以此类库经常应用于MVC开发模式的Java Web程序。
代码审计实战3-android  java
FreeMarker 模板文件与 HTML 一样都是静态页面,当用户访问页面时,FreeMarker 引擎会进行解析并动态替换模板中的内容进行渲染,然后将渲染后的结果返回到浏览器中。

FreeMarker 模板语言(FreeMarker Template Language,FTL)由 4 个部分组成:

  • 文本:包括HTML标签与静态文本等静态内容,会原样输出;
  • 插值:这部分的输出会被计算的数据来替换,使用${}这种语法;
  • 标签:给FreeMarker的指示,可以简单与指令等同,不会打印在内容中,比如<#assign name=‘bob’>;
  • 注释:由<#–和–>表示,不会被freemarker处理。

FreeMarker模版内容示例:

<html>
<head><title>Welcome TeamsSix!</title>
</head>
<body> <#-- 这是注释 -->
<h1>Welcome !</h1>
<p>Our latest product:<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>

1、漏洞原理:
服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。

springboot防止XSS攻击和sql注入

https://blog.csdn.net/china_coding/article/details/129680240

①:创建Xss请求过滤类XssHttpServletRequestWraper
代码审计实战3-android  java

public class XssHttpServletRequestWraper extends HttpServletRequestWrapper {Logger log = LoggerFactory.getLogger(this.getClass());public XssHttpServletRequestWraper() {super(null);}public XssHttpServletRequestWraper(HttpServletRequest httpservletrequest) {super(httpservletrequest);}//过滤springmvc中的 @RequestParam 注解中的参数public String[] getParameterValues(String s) {String str[] = super.getParameterValues(s);if (str == null) {return null;}int i = str.length;String as1[] = new String[i];for (int j = 0; j < i; j++) {//System.out.println("getParameterValues:"+str[j]);as1[j] = cleanXSS(cleanSQLInject(str[j]));}log.info("XssHttpServletRequestWraper净化后的请求为:==========" + as1);return as1;}//过滤request.getParameter的参数public String getParameter(String s) {String s1 = super.getParameter(s);if (s1 == null) {return null;} else {String s2 = cleanXSS(cleanSQLInject(s1));log.info("XssHttpServletRequestWraper净化后的请求为:==========" + s2);return s2;}}//过滤请求体 json 格式的@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());return new ServletInputStream() {@Overridepublic int read() throws IOException {return bais.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) { }};}public   String inputHandlers(ServletInputStream servletInputStream){StringBuilder sb = new StringBuilder();BufferedReader reader = null;try {reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8")));String line = "";while ((line = reader.readLine()) != null) {sb.append(line);}} catch (IOException e) {e.printStackTrace();} finally {if (servletInputStream != null) {try {servletInputStream.close();} catch (IOException e) {e.printStackTrace();}}if (reader != null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}return  cleanXSS(sb.toString ());}public String cleanXSS(String src) {String temp = src;src = src.replaceAll("<", "<").replaceAll(">", ">");src = src.replaceAll("\\\\(", "(").replaceAll("\\\\)", ")");src = src.replaceAll("'", "'");src = src.replaceAll(";", ";");//bgh 2018/05/30  新增/**-----------------------start--------------------------*/src = src.replaceAll("<", "& lt;").replaceAll(">", "& gt;");src = src.replaceAll("\\\\(", "& #40;").replaceAll("\\\\)", "& #41");src = src.replaceAll("eval\\\\((.*)\\\\)", "");src = src.replaceAll("[\\\\\\"\\\\\\'][\\\\s]*javascript:(.*)[\\\\\\"\\\\\\']", "\\"\\"");src = src.replaceAll("script", "");src = src.replaceAll("link", "");src = src.replaceAll("frame", "");/**-----------------------end--------------------------*/Pattern pattern = Pattern.compile("(eval\\\\((.*)\\\\)|script)",Pattern.CASE_INSENSITIVE);Matcher matcher = pattern.matcher(src);src = matcher.replaceAll("");pattern = Pattern.compile("[\\\\\\"\\\\'][\\\\s]*javascript:(.*)[\\\\\\"\\\\']",Pattern.CASE_INSENSITIVE);matcher = pattern.matcher(src);src = matcher.replaceAll("\\"\\"");// 增加脚本src = src.replaceAll("script", "").replaceAll(";", "")/*.replaceAll("\\"", "").replaceAll("@", "")*/.replaceAll("0x0d", "").replaceAll("0x0a", "");if (!temp.equals(src)) {// System.out.println("输入信息存在xss攻击!");// System.out.println("原始输入信息-->" + temp);// System.out.println("处理后信息-->" + src);log.error("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");log.error("原始输入信息-->" + temp);throw new CustomerException("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");}return src;}//输出public void outputMsgByOutputStream(HttpServletResponse response, String msg) throws IOException {ServletOutputStream outputStream = response.getOutputStream(); //获取输出流response.setHeader("content-type", "text/html;charset=UTF-8"); //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码byte[] dataByteArr = msg.getBytes("UTF-8");// 将字符转换成字节数组,指定以UTF-8编码进行转换outputStream.write(dataByteArr);// 使用OutputStream流向客户端输出字节数组}// 需要增加通配,过滤大小写组合public String cleanSQLInject(String src) {String lowSrc = src.toLowerCase();String temp = src;String lowSrcAfter = lowSrc.replaceAll("insert", "forbidI").replaceAll("select", "forbidS").replaceAll("update", "forbidU").replaceAll("delete", "forbidD").replaceAll("and", "forbidA").replaceAll("or", "forbidO");if (!lowSrcAfter.equals(lowSrc)) {log.error("sql注入检查:输入信息存在SQL攻击!");log.error("原始输入信息-->" + temp);log.error("处理后信息-->" + lowSrc);throw new CustomerException("sql注入检查:参数含有非法攻击字符,已禁止继续访问!!");}return src;}}

②:把请求过滤类XssHttpServletRequestWraper添加到Filter中,注入容器

@Component
public class XssFilter implements Filter {Logger log  = LoggerFactory.getLogger(this.getClass());// 忽略权限检查的url地址private final String[] excludeUrls = new String[]{"null"};public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) arg0;HttpServletResponse response = (HttpServletResponse) arg1;String pathInfo = req.getPathInfo() == null ? "" : req.getPathInfo();//获取请求url的后两层String url = req.getServletPath() + pathInfo;//获取请求你ip后的全部路径String uri = req.getRequestURI();//注入xss过滤器实例XssHttpServletRequestWraper reqW = new XssHttpServletRequestWraper(req);//过滤掉不需要的Xss校验的地址for (String str : excludeUrls) {if (uri.indexOf(str) >= 0) {arg2.doFilter(arg0, response);return;}}//过滤arg2.doFilter(reqW, response);}public void destroy() {}public void init(FilterConfig filterconfig1) throws ServletException {}
}

上述代码已经可以完成请求参数、JSON请求体的过滤,但对于json请求体还有其他的方式实现。

扩展:
还可以重写spring中的MappingJackson2HttpMessageConverter来过滤Json请求体

因为请求体在进出Contoroller时,会经过MappingJackson2HttpMessageConverter的一个转换,把请求体转换成我们需要的json格式,所以可以在这里边做一些修改!

@Configuration
public class MyConfiguration {@Beanpublic MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){//自定义转换器MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();//转换器日期格式设置ObjectMapper objectMapper = new ObjectMapper();SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");objectMapper.setDateFormat(smt);converter.setObjectMapper(objectMapper);//转换器添加自定义Module扩展,主要是在这里做XSS过滤的!!,其他的是其他业务,不用看SimpleModule simpleModule = new SimpleModule();//添加过滤逻辑类!simpleModule.addDeserializer(String.class,new StringDeserializer());converter.getObjectMapper().registerModule(simpleModule);//设置中文编码格式List<MediaType> list = new ArrayList<>();list.add(MediaType.APPLICATION_JSON_UTF8);converter.setSupportedMediaTypes(list);return converter;}}

真正的过滤逻辑类StringDeserializer:

//检验请求体的参数
@Component
public class StringDeserializer extends JsonDeserializer<String> {@Overridepublic String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {String str = jsonParser.getText().trim();//sql注入拦截if (sqlInject(str)) {throw new CustomerException("参数含有非法攻击字符,已禁止继续访问!");}return xssClean(str);}public boolean sqlInject(String str) {if (StringUtils.isEmpty(str)) {return false;}//去掉'|"|;|\\字符str = org.apache.commons.lang3.StringUtils.replace(str, "'", "");str = org.apache.commons.lang3.StringUtils.replace(str, "\\"", "");str = org.apache.commons.lang3.StringUtils.replace(str, ";", "");str = org.apache.commons.lang3.StringUtils.replace(str, "\\\\", "");//转换成小写str = str.toLowerCase();//非法字符String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alert","alter", "drop"};//判断是否包含非法字符for (String keyword : keywords) {if (str.indexOf(keyword) != -1) {return true;}}return false;}//xss攻击拦截public String xssClean(String value) {if (value == null || "".equals(value)) {return value;}//非法字符String[] keywords = {"<", ">", "<>", "()", ")", "(", "javascript:", "script","alter", "''","'"};//判断是否包含非法字符for (String keyword : keywords) {if (value.indexOf(keyword) != -1) {throw new CustomerException("参数含有非法攻击字符,已禁止继续访问!");}}return value;}
}

使用这种形式也可以完成json请求体的过滤,但个人更推荐使用XssHttpServletRequestWraper的形式来完成xss过滤

Mybatis 在 Mapper.xml 文件中的转义字符处理方式

在mybatis的映射文件写sql时,很多时候都需要写一些特殊的字符。例如:“<” 字符 “>” 字符 “>=” 字符 “<=” 字符,但是在xml文件中并不能直接写上述列举的字符,否则就会报错。
1、使用<![CDATA[ ]]>来解决

<select id = "selectUserByAge" resultType="com.test.hiioc.model.UserTable" >selectid,userName,agefromuserTable<where>IS_DELETE = 1/*时间段查询*/<if test = "userTable.startDate != null">and SIGNING_DATE <![CDATA[>=]]> #{userTable.startDate}</if><if test = "userTable.endDate!=null">and SIGNING_DATE <![CDATA[<=]]> #{userTable.endDate}</if></where>
</select>

2、
代码审计实战3-android  java

<select id = "selectUserByAge" resultType="com.test.hiioc.model.UserTable" >selectid,userName,agefromuserTable<where>IS_DELETE = 1/*时间段查询*/<if test = "userTable.startDate!=null">and SIGNING_DATE &gt;= #{userTable.startDate}</if><if test = "userTable.endDate != null">and SIGNING_DATE &lt;= #{userTable.endDate}</if></where>
</select>