任务描述
本关任务:使用servlet
生成验证码。
相关知识
验证码在我们登陆、注册网站,火车票买票的时候经常会见到的,为什么要有验证码呢?可能很多人都会有这个疑问。
但是作为开发者,可能我们更多的就会关注怎么生成验证码了。
要了解如何生成验证码,我们首先要知道什么是验证码,网站为什么需要它。
为什么要有验证码,什么是验证码
我们经常需要在网站或者应用程序中填写验证码,不过作为用户而言其实我们一点都不喜欢验证码,因为有时候老容易填错。
为什么这个影响用户体验的东西还是一直存在呢?
肯定是有道理的。
一个网站除了我们人操作电脑可以登录之外,使用JavaScript
代码和一些脚本语言也是可以登录的,但是我们开发网站是给人用的而不是给机器使用的,我们想象一个网站如果没有验证码,我们只需要编写一段脚本就可以无限次数的登陆某个网站,这样无数次的尝试就可以暴力破解用户的密码,如果是注册行为那就会给网站制造很多垃圾信息,这个就会对该网站造成极大的资源浪费,严重的可能会让这个网站崩溃,所以就有了验证码。
说白了,验证码就是用来判断是人在操作还是机器在操作。
如何使用Servlet生成验证码
在Java
中我们可以在Web
项目中使用Servlet
来生成验证码,流程是:前端请求验证码servlet
对应的地址,后端servlet
收到请求,生成一串字符作为验证码,存入到Session
中,最后将验证码作为一张图片返回给前端。前端填写了验证码提交到服务器来验证。
我们看一个示例,你也可以根据这个示例在右侧编辑器中一步一步实现验证码的功能。
项目和servlet
已经创建好了,我们首先在web.xml
文件中注册servlet
。
如下:
在servlet
的doGet()
方法中编写代码实现生成图片验证码:
分为如下步骤:
定义图像数据缓冲区(BufferedImage
);
创建图片对象;
创建绘制工具(Graphics
);
生成随机数,存入到session
中;
使用Graphics
绘制图形;
将验证码通过图像输出流(ImageIO
)输出到客户端;
最后输入验证码地址即可访问单验证码。
具体代码如下:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 使用验证码的步骤
// 定义图片的宽高
int height = 20;
int width = 60;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 绘图工具
Graphics graphics = image.getGraphics();
// 绘制矩形
graphics.setColor(getRandColor());
// 绘制矩形背景 前两个参数 是 x y的坐标
graphics.fillRect(0, 0, width, height);
// 设置文字的颜色 为白色
graphics.setColor(Color.WHITE);
String yzm = "";
// 生成四个随机数字并且画在图片上
for (int i = 1; i <= 4; i++) {
// 生成随机数字并且显示到页面上
int number = new Random().nextInt(10);
yzm += number;
graphics.drawString(number + "", 10 * i, 10);
}
// 将验证码放入Httpsession中
HttpSession session = req.getSession();
session.setAttribute("sessionYzm", yzm);
// 将验证码图片输出到客户端
ImageIO.write(image, "jpg", resp.getOutputStream());
}
// 获取随机颜色
private Color getRandColor() {
int red = new Random().nextInt(255);
int green = new Random().nextInt(255);
int blue = new Random().nextInt(255);
return new Color(red, green, blue);
}
编程要求
web.xml
中的代码已经添加,按照上述步骤编写servlet
代码,点击测评即可。
效果图:
测试说明
因为需要部署服务器,并且运行测试代码,所以评测时间较长,需要30
秒左右。
开始你的任务吧,祝你成功!
package com.servlet;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class CodeServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {/********* Begin *********/
//请在此编写生成验证码的代码
int height=20;
int width=60;
BufferedImage image =new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Graphics graphics=image.getGraphics();
graphics.setColor(getRandColor());
graphics.fillRect(0,0,width,height);
graphics.setColor(Color.WHITE);
String yzm="";
for(int i=1;i<=4;i++){
int number=new Random().nextInt(10); yzm+=number; graphics.drawString(number+"",10*i,10);
}
HttpSession session=req.getSession();
session.setAttribute("sessionYzm",yzm);
ImageIO.write(image,"jpg",resp.getOutputStream());/********* End *********/
} // 获取随机颜色 private Color getRandColor() { int red = new Random().nextInt(255); int green = new Random().nextInt(255); int blue = new Random().nextInt(255); return new Color(red, green, blue); }
}
任务描述
本关任务:编写程序验证验证码是否正确。
相关知识
上一关我们已经学习如何生成验证码了,为了完成一整套的验证码使用流程我们还需要知道如何验证用户提交的验证码是否正确。
登录功能
我们经常在登录注册的时候填写验证码,本关我们就来实现登录功能。
首先我们来理解验证码校验的基本流程:
上图展示了一个用户填写验证码的基本流程,用户打开网页显示服务端生成的验证码,点击“看不清楚”标签可以重新生成,这个时候会从新请求服务端数据,服务端用Session
来保存验证码信息。
当用户点击确认按钮的时候,我们就需要对用户通过表单提交的验证码进行校验了,这个时候服务端获取Session
保存的验证码信息和用户提交的验证码数据进行校验如果两者一致则校验通过。
这就是一个完整的验证码流程了。
我们可以将验证码的流程总结为:前端表单登陆 => 后端获取到验证码校验 => 前端收到后端的响应。
借下来我们来实现这个过程。
前端实现
我们创建一个登录表单,代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
登录
用户名:
密 码:
验证码:
//重新加载验证码
function reload() {
var img = document.getElementById("yzmImg");
img.src = "code?" + new Date().getTime();
}
效果如下:
后端校验
前端页面写好之后我们就可以验证在后端编写代码来验证,表单传递过来的参数是否正确了。
步骤如下:
接收用户传递的参数:username,password,verifycode
;
判断验证码是否正确;
如果验证码正确则判断用户名和密码是否正确。否则提示客户端验证码错误;
用户名密码正确,回传给客户端“登录成功”。
校验验证码的核心代码如下:
编程要求
好了,该你啦,使用本关所学内容,完成登录的校验,具体要求如下:
首先实现验证码校验的功能,当验证码填写错误的时候,给前端返回数据“验证码错误”;
当用户名为admin
,密码为admin123
时可以登录成功,返回“登录成功”,其他情况返回“登录失败”;
前端页面已经编写完成,你需要编写的是后端代码。
测试说明
本次实训使用的是junit+cactus
框架进行测试,所有测试集通过即算通关。
开始你的任务吧,祝你成功!
package com.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8"); /********* Begin *********///请在此进行登录校验
PrintWriter writer=resp.getWriter();
String verifycode=req.getParameter("verifycode");
String username=req.getParameter("username");
String password=req.getParameter("password");
HttpSession session =req.getSession();
String realCode=(String)session.getAttribute("sessionYzm");
if(username.equals("admin")&&password.equals("admin123")&&verifycode.equals(realCode)){writer.write("登录成功");}
else if(!verifycode.equals(realCode)){ writer.write("验证码错误");}
else if(verifycode.equals(realCode)){
writer.write("验证码正确");
} else{ writer.write("登录失败"); }/********* Begin *********/
} protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); }
}
任务描述
本关任务:使用Kaptcha
组件生成验证码,并校验验证码是否正确。
相关知识
之前两关我们已经了解了验证码的制作流程,不过我们在开发中一般不会去自己从零开始编写验证码,而是会使用到开源的组件,本关我们就来使用Kaptcha
来生成验证码,并且编写一个页面校验用户的验证码是否输入正确。
Kaptcha 组件的使用
先来看要实现的效果:
首先制作用户填写验证码的页面captchacode.jsp
function reloadCode() {
var date = new Date().getTime();
document.getElementById("code").src = "<%=request.getContextPath() %>/imageKaptcha?d="+date;
}
看不清
接着我们写一个检查验证码输入是否正确的类checkCaptchaServlet.java
request.setCha\fracterEncoding("utf-8");
// 获取Kaptcha jar包里面的KAPTCHA_SESSION_KEY
String trueCaptcha = (String)session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
String inputCaptcha = request.getParameter("captcha");
if(trueCaptcha.toLowerCase().equals(inputCaptcha.toLowerCase())) {
out.write("验证码输入正确");
} else {
out.write("验证码输入错误");
}
然后配置好web.xml
就ok
了。下面我们来看看怎么配置web.xml
myCaptcha
com.google.code.kaptcha.servlet.KaptchaServlet
CheckCaptcha
com.servlet.CheckCaptchaServlet
myCaptcha
/imageKaptcha
CheckCaptcha
/checkCaptcha
做完上述步骤之后,运行项目,打开网页,即可查看验证码。
输入正确的验证码点击submit
:
经过上述步骤我们就使用Kaptcha
组件生成验证码了。
扩展:Kaptcha
还有很多其他的设置可以实现图片边框,边框颜色,中文验证码等操作,限于篇幅在这里就不在赘述。
编程要求
好了,到你啦,来使用Kaptcha
生成验证码并校验输入的验证码是否正确吧。
补全captchacode.jsp
,实现验证码表单的页面效果;
补全CheckCaptchaServlet
,实现验证码的校验功能,验证码正确返回:验证码输入正确
,否则返回:验证码输入错误
。
开始你的任务吧,祝你成功!
package com.servlet;
import com.google.code.kaptcha.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
public class CheckCaptchaServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setContentType("text/html;charset=utf-8");/********* Begin *********/ //校验kaptcha 验证码是否正确 //获取Kaptcha jar包里面的KAPTCHA_SESSION_KEY // request.setCha\fracterEncoding("utf-8");
HttpSession session=req.getSession();
String trueCaptcha=(String)session.getAttribute(Constants.KAPTCHA_SESSION_KEY); PrintWriter out=resp.getWriter(); String inputCaptcha=req.getParameter("captcha");if(trueCaptcha.toLowerCase().equals(inputCaptcha.toLowerCase())){ out.write("验证码输入正确");
}else{ out.write("验证码输入错误"); }/********* End *********/ }@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { doGet(req, resp);}}
本关任务:
使用 qira 拿到属于你的 flag !!!
为了完成本关任务,你需要掌握:
1、正确的程序分析思路 2、动态调试器 qira 的使用
程序分析
在本关学习新知识之前,我们需要先知道如何分析一个程序,即拿到一个二进制程序文件后应当如何入手。 对一个程序的分析应当从以下三步入手:运行程序 --> 静态分析 --> 动态分析。运行程序,即在本机上直接和程序进行交互,观察程序的输入和输出,作为分析人员的我们应当尽可能的执行完所有的功能。静态分析,即利用静态分析软件,如:IDA Pro,尝试对程序进行反汇编、反编译,分析伪代码,结合上一步程序运行的输入和输出快速定位有问题的代码段。动态分析,即利用动态分析软件,如:qira ,尝试观察程序动态运行的结果,能够直接观察到程序运行过程中寄存器、栈、堆等数据的变化。 接下来我们将用一个例子来完整的走一遍程序分析的流程,示例文件为 demo ,可以在目录/home/headless/Desktop/workspace/myshixun/pwnPro/step2/
下找到。 首先,打开终端,如下终端命令所示,进入到程序所在目录,查看程序信息,发现程序没有可执行权限,添加可执行权限,然后执行程序。
cd /home/headless/Desktop/workspace/myshixun/pwnPro/step2/
ls -all demo
chmod +x demo
./demo
如下截图所示,发现程序运行后,只打印了No flag here!!!
,难道真的没有 flag ,我肯定是不信的,接着我们用 IDA 来分析。
按照上一关所学的内容,我们在 IDA 中打开 demo 文件,然后在反汇编界面按下 F5 ,直接观察伪代码。如下截图所示,是 demo 程序的主逻辑,最外层是一个 for 循环,将会执行18次,每次都对 false_flag 数组中的值进行了判断,根据判断的结果对 false_flag 数组元素的值进行变化,然后再将其赋值给 true_flag ,最后调用 puts 打印了No flag here!!!
。根据题目逻辑,false_flag 是待变换的数组,true_flag 才是存储真正 flag 的地方,程序通过对 false_flag 进行变化然后得到 true_flag ,所以我们需要的是 true_flag 的值,然而这里并没有打印该值。
我们继续分析,在 IDA 中我们可以通过点击直接跳转到对应变量所在位置,这里我们点击 false_flag 将会跳转到其位置所在。如下截图所示,可以发现 false_flag 位于 data 段,表明其是一个全局变量,后面的箭头所指dq offset aXdsyIkwHz3Eajs
表明其是一个指针变量,指向的内容我们可以看后面的方框,这是 IDA 给的注释,表明其指向的字符串是xdsy{Ikw_Hz3_Eajs}
。
我们再点击 true_flag ,如下截图所示,发现其位于 bss 段,表明其是未初始化的全局变量。箭头指向的位置标注了_BYTE_true_flag[19]
表明其是一个长为19的字符数组。当然这里并不能看到其值是啥,因为需要程序运行结束后才能观察到。如果我们想要得到这里的 true_flag ,有两种办法:一是根据程序的逻辑,自己编写求解脚本来得到 flag ,编写脚本的方式也很简单,直接复现程序的逻辑即可;二是使用动态调试,因为 true_flag 是程序的一个变量,在程序运行到某一时刻,该变量中一定存储着正确的 flag 。因此在下一小节,我们将会使用 qira 动态调试来直接得到 true_flag。
动态调试器 qira
什么是 qira
qira 在学习动态调试之前,我们先来了解下 qira 。qira 是 github 上的一个开源项目,在上方已经给出了其链接,点击即可跳转到 github ,github 中有详细的介绍和其安装方式。当然在本实验平台中,我们已经安装好了。qira 被誉为超越时空的调试器,即可以在时间中任意穿梭的动态调试器,实质上是一个 trace 工具,将程序整个执行流全部记录下来,然后给予用户回溯、查看命中断点的所有指令(即交叉引用)等。
qira 的基本使用
打开实验环境的终端,切换到 demo 文件所在的目录下,使用 qira 启动程序,整个过程如下命令所示。
cd /home/headless/Desktop/wordspace/myshixun/pwnPro/step2/
qira demo
启动后,终端输出如下截图所示,可以看到箭头所指的地方,分别是程序所在路径(这里运行时的环境和实验平台不一样,请用自己在平台的输出进行验证),监听的 ip+port ,以及程序自身的运行输出。值得说明的是,qira 采用 web 页面来展示整个程序的执行过程,后端使用 python flask 框架实现。
之后,我们打开浏览器,输入127.0.0.1:3002
即可看到 qira 的界面,如下截图所示。序号1指示的是 qira 的一条时间线( vtimeline ),在调试复杂程序的时候程序并不会一次执行完,我们每次步入都需要在 web 页面进行刷新,此时就会出现新的时间线。序号2指示的是 qira 的一些控制数据,前三个盒子中展示的内容分别是:第几条指令、第几条时间线、指令地址,我们可以直接修改盒子中的数据来实现跳转。序号3指示的是程序的汇编指令,这里展示的指令和 IDA 中的没有什么区别,当然地址会有所不同,这是因为程序动态运行加上了基地址所导致的。序号4指示的是寄存器的值,序号5指示的是程序运行过程中的函数堆栈,从这里我们可以看到程序的函数调用关系。
接下来,我们再看看 qira 的强大之处,在 qira 的主界面上,我们可以通过点击位于内存中的地址查看当前内存的数据信息。如下截图所示,我们点击寄存器中的 RSP 栈顶指针寄存器,此时最下方就会展示当前栈帧的内存数据信息,图中方框和箭头圈出的地方刚好是 RSP 指向的8个字节内存数据,后面显示的乱码就是对应字节的 ascii 编码信息,转换不出来就显示 . 号。
使用 qira 找到 flag
在之前的介绍中,我们知道了 qira 是一个 trace 工具,它会记录程序运行过程中所有寄存器和内存数据的变化,因此通过它,我们就可以在内存中找到 true_flag ,从而无需编写脚本就能获得 flag 。 在实践之前,这里我们先引入基址这个概念。如下截图所示,我们同时打开 IDA 和 qira ,将 IDA 汇编代码定位到 start 函数,将 qira 指令定位到第0条。我们知道 c 语言中 main 函数是程序的入口点,其实在 main 函数之前还会调用 start 函数,做二进制分析的时候我们应当认为这才是程序真正的入口点。通过对比汇编指令,我们可以看到 qira 就是从 start 开始执行的,但是我们也会发现 IDA 显示的地址是0x0000000000001060
,而 qira 显示的却是0x4000001060
。 我们知道64位程序的地址都是 64bit ,十六进制表示的话就是16个十六进制数,高位为0可以省略。因此这里 qira 的地址比 IDA 多了0x4000000000
,这个地址我们就称为基址,所有汇编指令在运行时的地址都是基址 + 偏移,而 IDA 是静态分析,所以只显示偏移,没有加上这个基址。
在弄清楚基址这个概念后,寻找 flag 就方便多了,首先我们在 IDA 中找到 true_flag 的地址,如下截图所示,为4030
,然后我们将基址加上就是0x4000004030
,之后我们将其输入到 qira 最上控制面板的第四个盒子中,截图如下。
之前并没有介绍这第四个盒子的作用,其实该盒子的作用就是用于展示对应地址的内存数据信息。按照上面的步骤,我们在 qira 中输入 true_flag 的地址后,直接回车,此时可以看到最下方已经出现了对应地址的内存数据信息,如下截图所示,可以看到程序运行后的 flag 了。
好了,到这里我们已经基本掌握了程序的分析步骤,qira 动态调试器的使用,接下来就用学到的知识去完成本关挑战吧。
本课程采用了 CTF 比赛获取 flag 的方式来进行实践练习,你的目标是拿到一个 flag ,形式为flag{xxxxxxxxxxxxxxx}
,本关目标文件为/home/headless/Desktop/workspace/myshixun/pwnPro/step2/
目录下的 level2 。 本关没有编程要求,但需要你通过静态分析 + 动态调试拿到隐藏在二进制程序中的 flag ,后面的课程将需要你利用程序漏洞进行编程获取到 shell ,进一步拿到 flag 。
将你拿到的 flag 写入到实验环境提供的 flag.txt 文件中,然后点击评测即可。
开始你的任务吧,祝你成功!
打开终端
cd /home/headless/Desktop/workspace/myshixun/pwnPro/step2/
vim flag.txt
按i,输入flag{Y0u_Are_Great},按Esc再:wq保存退出即可
选择载体HTML文件,位于data/workspace/myshixun/xxaqcenter.html
用编辑器打开xxaqcenter.html这个文件
在上述的HTML 文件中隐藏 I LOVE YOU ,将I LOVE YOU 转换成ASCII码二进制形式为:01001001 01001100 01001111 01010110 01000101 01011001 01001111 01010101
(1)利用标记中属性赋值号“=”左右添加空格来隐藏信息。以左右都无空格表示00,左无右有空格表示01,左有右无空格表示10,左右均有空格表示11,则一个属性赋值可隐藏2bit信息。 (2)标记名称(除和