[ffffffff0x] 浅谈web安全之前端加密

2020-12-25 17:49:45 浏览数 (1)

简介

数学是现代加密的基础,而加密,是现代一切认证实现的基础。

在web中,很多场景登陆的账号密码请求都是经过 js 加密之后再发送,如图

通过burp抓包可以看到加密信息. 很明显可以看到 password 参数的值是经过前端加密之后再进行传输的,遇到这种情况,普通发包的爆破脚本就很难爆破成功。所以我们需要明白基础的加密概念,与常见的加密方式。

下面总结几种常见的加密,与真实案例中的加密对抗。


几种加密类型

ASCII 编码

ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符。

JS代码示例

代码语言:txt复制
document.write(escape("password") )
var code=unescape("112 97 115 115 119 111 114 100");
eval(code)  

PHP代码示例

代码语言:txt复制
$urlencode =_GET[passwd]
// urlencode 加密
$urlencode = urlencode($str);
echo $urlencode . '</br>';
// urldecode 解密
$urldecode = urldecode($urlencode);
echo $urldecode; // 要加密的字符串

JAVA代码示例

代码语言:txt复制
public String EnCode(String a,String s)
{
    s = CheckSelectItem(s);//分隔符获取
    StringBuilder ASCIIL = new StringBuilder();//结果保存
    StringBuilder ssvb = new StringBuilder();//输入值
    String[] ss = a.split("");
    for (String ik : ss) {
        ssvb.append(ik);
    }
    char[] chars = ssvb.toString().toCharArray();//输入值保存为char数组
    for (int i = 0; i < chars.length; i  ) {
        if(i != chars.length - 1)
        {
            ASCIIL.append(Integer.valueOf(chars[i])).append(s);
        }
        else {
            ASCIIL.append(Integer.valueOf(chars[i]));
        }
    }//ASCII转换
    return ASCIIL.toString();
}

public String DeCode(String a,String s)
{
    s = CheckSelectItem(s);//分隔符获取
    String out;//输出
    if(s.equals(""))
    {
        out = "解密请选择空格分割或换行分割";
    }
    else
        {
    StringBuilder sbu = new StringBuilder();
    String[] chars = a.split(s);//输入值切片
    for (String aChar : chars) {
        sbu.append((char) Integer.parseInt(aChar));
    }//ASCII转换
    out = sbu.toString();
    }
    return out;
}

private String CheckSelectItem(String i)
{
    String delc;
    switch (i)
    {
        case "NULL": delc = "";break;
        case "    Space": delc = " ";break;
        case "\n Newline": delc = "n";break;
        default: delc = i;
    }
    return delc;
}//返回分隔符

Hash算法

Hash 算法是一个广义的算法,也可以认为是一种思想,使用Hash算法可以提高存储空间的利用率,可以提高数据的查询效率,也可以做数字签名 来保障数据传递的安全性。所以Hash算法被广泛地应用在互联网应用中。

MD5 和 SHA1 是曾经被广泛应用的Hash函数,而它们都是以 MD4 为基础设计的。

MD5(Message Digest Algorithm MD5),将输入以512位分组,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,函数的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

JS代码示例

代码语言:txt复制
var b =$("#logPassword");$.md5(b.val());

PHP代码示例

代码语言:txt复制
// md5 加密
$md5 = md5($str);
echo $md5 . '</br>';  

JAVA代码示例

代码语言:txt复制
public String HashEncode(String message,String check)

{
MessageDigest mc = null;//实例化MessageDigest方法用来作MD5转换
String checking;
checking = check;
try {
mc = MessageDigest.getInstance(checking);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}

assert mc != null;
mc.update(message.getBytes());//hash摘要
System.out.println(java.util.Base64.getEncoder().encodeToString(mc.digest()));
}
public static void main(String[] args) {
Hash hash = new Hash();
hash.HashEncode("加密前文本","MD5");
}

Base64

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。

编码规则: 是将3个8位字节(3×8=24位)编码成4个6位的字节(4×6=24位),之后在每个6位字节前面,补充两个0,形成4个8位字节的形式,那么取值范围就变成了0~63。又因为2的6次方等于64,所以每6个位组成一个单元。

base64既可以加密也可以解密,导入base64.js然后在js脚本中

JS代码示例

代码语言:txt复制
var b =$("#logPassword");var code = $.encode64(b.val());$.unencode64(code); //加密和解密

PHP代码示例

代码语言:txt复制
<?php
$cany = $_GET[password];#定义要加密的字符串
echo base64_encode($cany); #输出加密后的字符串
echo base64_decode($cany); #输出解密后的字符串
?>

JAVA代码示例

代码语言:txt复制
package All_Tool_List.Coding;

import java.nio.charset.StandardCharsets;

public class Base64 {
public String Base64Decode(String in)
{
byte[] bs64 = java.util.Base64.getDecoder().decode(in);//获取用户输入字符通过base64加密,输出byte数组型值
return new String(bs64, StandardCharsets.UTF_8);//将byte数组转换成String输出
}

public String Base64Encode(String in)
{
byte[] bytes = in.getBytes(StandardCharsets.UTF_8);//将用户输入字符转换成byte(utf-8编码)
return java.util.Base64.getEncoder().encodeToString(bytes);//输出值
}
}

下面配合实战案例来看一看


实战案例

前端加密示例

在一次实战中,登录页面输入admin admin,抓包如下

乍一看,不知道它使用了什么加密方式。

代码语言:txt复制
admin  admin
["=96=98=106=101=105","=96=98=106=101=105","0"]

仔细分析,尝试修改密码字段为abcde,12345,继续抓包

代码语言:txt复制
admin abcde
["=96=98=106=101=105","=96=96=96=96=96","0"]
admin 12345
["=96=98=106=101=105","=48=48=48=48=48","0"]

这个时候,对编码比较熟悉的人已经发现了,其实就是简单的ASCII码减去其所在位数,即第一位字符 a 的 ascii 减一为96,第二位字符 d 的 ascii 减二为98,以此类推,最后以 = 分隔每个字符即可。知道了加密规则后,代码就容易写出了。

JAVA代码示例

只需将密码本放在C盘下命名为pass.txt,程序会自动生成加密后的结果在控制台输出。

代码语言:txt复制
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
public class ASCIIM1EC {
    public static void main(String[] args) {
        FileReader("C:pass.txt");
    }
    public static String ASCIIEnCode(String a)
    {
        String s = "=";
        String out;
        StringBuilder ASCIIL = new StringBuilder("=");
        StringBuilder ssvb = new StringBuilder();
        String[] ss = a.split(s);
        for (String ik : ss) {
            ssvb.append(ik);
        }
        String ika = ssvb.toString();
        char[] chars = ika.toCharArray();
        int k =1;
        for (int i = 0; i < chars.length; i  ) {
            if(i != chars.length - 1)
            {
                ASCIIL.append(((int)chars[i])-k).append(s);
            }
            else {
                ASCIIL.append(((int)chars[i])-k);
            }
            k  ;
        }
        k =1;
        out = ASCIIL.toString();
        return out;
    }
    public static int[] FileReader(String file) {
        Charset charset = Charset.forName("US-ASCII");
        try (BufferedReader reader = Files.newBufferedReader(Paths.get(file), charset)) {
            String line = null;
            StringBuilder sb = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                System.out.println(ASCIIEnCode(line));
            }
        } catch (IOException x) {
            System.err.format("IOException: %s%n", x);
        }
        return null;
    }
}

示例密码表

输出结果

代码语言:txt复制
=48=48=48=48=48=48
=96=98=106=101=105
=111=95=112=111=114=105=107=92
=111=95=112=111
=48=48=48=48
=48=48=48=48=48=48=48=48=48
=106=95=112=108=106
=110=98=102=111=105=108=96
=48=48=48=48=117=114=92=107=91

可见,加密后的值和与案例中的基本吻合,剩下的就是配合代码与burp交互进行爆破

同时,我们以一个后端的反推案例为例

后端加密示例

在一次对 BlogEngine.NET 的站点进行渗透时,发现其存在CVE-2019-10718 XML实体注入漏洞。查找相应的exp,成功转储其存储用户信息的 users.xml

得到一串加密后的字符串: 91wzdr6ew 0xclxg1xsauvog6qeijwznsp1g/7ifm u=

在看到这串字符时第一眼也依旧看不出是什么加密,查找其托管在github上的后端源码后发现使用的是 SHA-256 BASE64 Hash加密。

后端源码链接:https://github.com/rxtur/BlogEngine.NET/blob/master/BlogEngine/

分析后尝试本地实现这个加密方法,java代码示例如下。

JAVA代码示例

代码语言:txt复制
public String HashEncode(String message,String check)
{
MessageDigest mc = null;//实例化MessageDigest方法用来作MD5转换
String checking;
checking = check;
try {
mc = MessageDigest.getInstance(checking);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
assert mc != null;
mc.update(message.getBytes());//hash摘要
System.out.println(java.util.Base64.getEncoder().encodeToString(mc.digest()));
}
public static void main(String[] args) {
Hash hash = new Hash();
hash.HashEncode("Password1","SHA-256");
}

输出结果

代码语言:txt复制
Password1 //明文
GVE/3J2k 3KkoF62aRdUjTyQ/5TVQZ4fI2PuqJ3 4d0= //密文

接下来就看你的明文字典够不够强大了。


总结

本文从几种常见的加密方式切入,对目前web前端加密的几种情况进行了概括。并结合真实案例给出了两种加密的对抗方法。对于web安全来说,前端的加密是事倍功半,应该真正把重心着眼于后端,当然前后端同时加固必然是最优解。由于篇幅有限,本文无法展开描述,后续会慢慢补充关于web加密的这部分内容。


本文作者 r0fus0d、RyuZU、the-fog

0 人点赞