記事ディレクトリ
3. SpringBoot で XSS 攻撃と SQL インジェクションを防ぐ方法
①:XssリクエストフィルタクラスXssHttpServletRequestWraperを作成する
②:リクエストフィルタリングクラスXssHttpServletRequestWraperをFilterに追加し、コンテナに注入する
1. XSS クロスサイト スクリプティング攻撃
①:XSS脆弱性入門
クロスサイト スクリプティング攻撃 (XSS) とは、攻撃者が Web ページに悪意のあるスクリプト コードを挿入することを指し、ユーザーがそのページを閲覧すると、Web ページに埋め込まれたスクリプト コードが解析されて実行され、悪意のある攻撃の目的が達成されます。ユーザー。XSS 攻撃はユーザーレベルの攻撃を目的としています。
②:XSS脆弱性分類
保存された XSS:保存された XSS、永続性、個人情報や公開記事などのコードがサーバーに保存され、コードを挿入します。フィルタリングがない場合、またはフィルタリングが厳密でない場合、これらのコードはサーバーに保存され、ユーザーがページがアクセスされたときにコードの実行をトリガーします。この種の XSS はより危険であり、ワームを引き起こしたり Cookie を盗んだりするのが簡単です。
反射型 XSS:非永続的で、XSS コードをトリガーするにはユーザーをだましてリンクをクリックさせる必要があります (サーバーにはそのようなページやコンテンツはありません)。一般に検索ページに簡単に表示されます。
DOM 型 XSS:バックエンドを経由せず、DOM-XSS 脆弱性は、Document Objeet Model
ドキュメント オブジェクト モデル (, DOM) に基づく脆弱性です。DOM-XSS は、トリガーを制御する URL 経由でパラメーターを渡すことによってトリガーされます。もリフレクション型 XSS です。
③:保護提案
-
ユーザー入力を制限します。フォーム データは値のタイプを指定します。たとえば、年齢は int のみ、名前は文字と数字の組み合わせです。
-
データに対して HTML エンコード処理を実行します。
-
特殊な HTML タグをフィルタリングまたは削除します。
-
JavaScript イベントをフィルタリングするためのタグ。
2. SQLインジェクション攻撃
①:SQLインジェクションの脆弱性紹介
SQL インジェクション (SQLi) は、悪意のある SQL ステートメントを実行するインジェクション攻撃です。攻撃者はデータベース クエリに任意の SQL コードを挿入することで、Web アプリケーションの背後にあるデータベース サーバーを完全に制御できるようになります。攻撃者は、SQL インジェクションの脆弱性を利用してアプリケーションのセキュリティ対策をバイパスしたり、Web ページや Web アプリケーションの認証と認可をバイパスして SQL データベース全体のコンテンツを取得したり、SQL インジェクションを使用してデータベース内のエントリを追加、変更、削除したりすることができます。記録。
SQL インジェクションの脆弱性は、MySQL、Oracle、SQL Server などの SQL データベースを使用する Web サイトまたは Web アプリケーションに影響を与える可能性があります。犯罪者はこれを使用して、顧客情報、個人データ、企業秘密、知的財産などのユーザーの機密データに不正にアクセスする可能性があります。SQL インジェクション攻撃は、Web アプリケーションの最も古く、最も一般的で、最も危険な脆弱性の 1 つです。
②:保護提案
mybatis を使用すると、#{}
SQL インジェクションを効果的に防ぐことができます。
-
使用する場合
#{}
:
<select id="getBlogById" resultType="Blog" parameterType=”int”>
select id,title,author,content
from blog where id=#{id}
</select>
実行された SQL ステートメントを出力すると、SQL が次のようになっていることがわかります。
select id,title,author,content from blog where id = ?
どのようなパラメータを入力しても、出力される SQL は次のようになります。これは、mybatis がプリコンパイル機能を有効にしているためで、SQL を実行する前に、上記の SQL がコンパイルのためにデータベースに送信されます。実行時には、コンパイルされた SQL を直接使用し、プレースホルダー "?" を置き換えます。SQL インジェクションはコンパイル処理でのみ機能するため、#{} のようにプリコンパイルされます? SQL インジェクションの問題を回避する方法は非常に優れています。
mybatis はどのようにして SQL プリコンパイルを実現しますか?
実際、フレームワークの一番下で動作しているのは jdbc のクラスであり、私たちがよく知っている Statement のサブクラスであり、そのオブジェクトにはコンパイルされた SQL ステートメントが含まれています。この「準備完了」メソッドにより、セキュリティが向上するだけでなく、SQL がコンパイルされており、再度実行するときに再コンパイルする必要がないため、SQL を複数回実行するときの効率も向上します。 PreparedStatement
PreparedStatement
-
使用する
${}
とき
<select id="orderBlog" resultType="Blog" parameterType=”map”>
select id,title,author,content
from blog order by ${orderParam}
</select>
インラインパラメータの形式が#{xxx}
" " から変更されていることに注意してください${xxx}
。orderParam
「 id」をパラメータ「 」に割り当てると、SQL は次のように出力されます。
select id,title,author,contet from blog order by id
明らかに、これでは SQL インジェクションを防ぐことはできず、パラメータは SQL コンパイルに直接関与するため、インジェクション攻撃を避けることはできません。ただし、動的なテーブル名と列名に関しては、${}
パラメータ形式「 」のみを使用できるため、インジェクションを防ぐために、そのようなパラメータをコード内で手動で処理する必要があります。
3. SpringBoot で XSS 攻撃と SQL インジェクションを防ぐ方法
早速、コードに進みましょう。
Xss 攻撃と SQL インジェクションの場合、ビジネス ニーズに応じてフィルターを使用して一部のリクエストを除去できます。
①:Xssリクエストフィルタリングクラスを作成する XssHttpServletRequestWraper
コードは以下のように表示されます。
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 格式的
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public 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(";", ";");
// 新增
/**-----------------------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リクエストボディを実現する方法は他にもありますので、興味のある方は以下の拡張機能をご覧ください。
より魅力的なコンテンツを入手するには、公式アカウント[Programmer Style]に注目してください!
拡張機能: Spring で Json リクエスト本文を書き換えて MappingJackson2HttpMessageConverter
フィルターすることもできます
リクエスト本文は、Contoroller に出入りするときに変換を経て、 MappingJackson2HttpMessageConverter
リクエスト本文を必要な JSON 形式に変換するため、ここでいくつかの変更を加えることができます。
@Configuration
public class MyConfiguration {
@Bean
public 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> {
@Override
public 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 フィルタリングを完了することをお勧めします。!