平常每次出一个漏洞都会出一个burp被动扫描的插件 所以可以提前做好一个模板

burp插件开发注意事项

  • 所有的burp插件都必须实现IBurpExtender这个接口
  • 实现类的包名称必须是burp
  • 实现类的名称必须是BurpExtender
  • 实现类比较是public的
  • 实现类必须有默认构造函数(public,无参),如果没有定义构造函数就是默认构造函数

    maven

    1. <dependency>
    2. <groupId>net.portswigger.burp.extender</groupId>
    3. <artifactId>burp-extender-api</artifactId>
    4. <version>LATEST</version>
    5. </dependency>

    打包

    image.png

模板

UI

设置一个大众化的UI

  1. package burp;
  2. import java.awt.Component;
  3. import java.io.PrintWriter;
  4. import java.net.URL;
  5. import java.util.ArrayList;
  6. import java.util.List;
  7. //import java.util.Random;
  8. import javax.swing.JScrollPane;
  9. import javax.swing.JSplitPane;
  10. import javax.swing.JTabbedPane;
  11. import javax.swing.JTable;
  12. import javax.swing.SwingUtilities;
  13. import javax.swing.table.AbstractTableModel;
  14. import javax.swing.table.TableModel;
  15. //IMessageEditor使用IMessageEditorController接口获取当前显示的消息的详细信息
  16. //扩展可以实现这个IScannerCheck接口,然后调用 IBurpExtenderCallbacks.registerScannerCheck()来注册一个自定义的 Scanner 检查。
  17. //Itab这个接口用于使用 IBurpExtenderCallbacks.addSuiteTab ()之类的方法为 Burp 提供将添加到 Burp 的 UI 中的自定义选项卡的详细信息。
  18. public class BurpExtender extends AbstractTableModel implements IBurpExtender, IScannerCheck, ITab, IMessageEditorController {
  19. //IBurpExtenderCallbacks接口类是IBurpExtender接口的实 现类与Burp其他各个组件(Scanner、Intruder、Spider......)、各个通信对象 (HttpRequestResponse、HttpService、SessionHandlingAction)之间的连接
  20. private IBurpExtenderCallbacks callbacks;
  21. //IExtensionHelpers接口包含许多辅助方法,扩展可以使用这些方法来协助 Burp 扩展出现的各种常见任务
  22. //比如向 HTTP 请求添加新参数,分析 HTTP 请求
  23. private IExtensionHelpers helpers;
  24. //分隔面板,SplitPane用于分隔两个(只能两个)组件,分割request和response
  25. private JSplitPane splitPane;
  26. //IMessageEditor用于为扩展提供 Burp 的 HTTP 消息编辑器的实例,以便扩展在其自己的 UI 中使用
  27. private IMessageEditor requestViewer;
  28. private IMessageEditor responseViewer;
  29. private final List<LogEntry> log = new ArrayList<>();
  30. //IHttpRequestResponse此接口用于检索和更新有关 HTTP 消息的详细信息。
  31. private IHttpRequestResponse currentlyDisplayedItem;
  32. private String ExtenderName = "SpringCore";
  33. private PrintWriter stdout;
  34. //此方法将在扩展加载后被调用,它注册了一个 IBurpExtenderCallbacks 接口的实例, IBurpExtenderCallbacks 接口提供了许多在开发插件过程中常用的一些操作。
  35. @Override
  36. public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) {
  37. //获取burp提供的标准输出流
  38. this.stdout = new PrintWriter(callbacks.getStdout(), true);
  39. //打印到标准输出流
  40. this.stdout.println(this.ExtenderName);
  41. //保留对callbacks的引用
  42. this.callbacks = callbacks;
  43. //此方法用于获取IExtensionHelpers对象,扩展程序可以使用该对象执行许多有用的任务。
  44. this.helpers = callbacks.getHelpers();
  45. //设置插件的名称
  46. callbacks.setExtensionName(this.ExtenderName);
  47. //调用 IBurpExtenderCallbacks.registerScannerCheck()来注册一个自定义的 Scanner 检查
  48. callbacks.registerScannerCheck(this);
  49. //创建一个Swing线程用于我们的burp的UI
  50. SwingUtilities.invokeLater(new Runnable() {
  51. @Override
  52. public void run() {
  53. BurpExtender.this.splitPane = new JSplitPane(0);
  54. BurpExtender.Table logTable = new BurpExtender.Table(BurpExtender.this);
  55. //创建一个滚动条
  56. JScrollPane scrollPane = new JScrollPane(logTable);
  57. //设置滚动条位置左侧或上方
  58. BurpExtender.this.splitPane.setLeftComponent(scrollPane);
  59. JTabbedPane tabs = new JTabbedPane();
  60. //此方法用于创建 Burp 的 HTTP 消息编辑器的新实例,以便扩展在其自己的 UI 中使用。
  61. BurpExtender.this.requestViewer = callbacks.createMessageEditor(BurpExtender.this, false);
  62. BurpExtender.this.responseViewer = callbacks.createMessageEditor(BurpExtender.this, false);
  63. tabs.addTab("Request", BurpExtender.this.requestViewer.getComponent());
  64. tabs.addTab("Response", BurpExtender.this.responseViewer.getComponent());
  65. //将组件设置在分隔线的右侧或下方
  66. BurpExtender.this.splitPane.setRightComponent(tabs);
  67. //customizeUiComponent方法用于自定义符合 Burp 的 UI 风格的 UI 组件,包括字体大小、颜色、表格行距等。
  68. callbacks.customizeUiComponent(BurpExtender.this.splitPane);
  69. callbacks.customizeUiComponent(logTable);
  70. callbacks.customizeUiComponent(scrollPane);
  71. callbacks.customizeUiComponent(tabs);
  72. //addSuiteTab方法用于将自定义选项卡添加到 Burp Suite 主窗口。
  73. callbacks.addSuiteTab(BurpExtender.this);
  74. }
  75. });
  76. }
  77. //设置我们的被动扫描
  78. @Override
  79. public List<IScanIssue> doPassiveScan(IHttpRequestResponse baseRequestResponse) {
  80. //等会要添加被动扫描代码
  81. return null;
  82. }
  83. //设置我们的主动扫描
  84. @Override
  85. public List<IScanIssue> doActiveScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) {
  86. return null;
  87. }
  88. //当报告同一URL有多个问题时,将调用此方法
  89. //漏洞去重函数,可以根据自己的需求,根据不同的参数来判断是否重复,比如URL、漏洞名称等
  90. @Override
  91. public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue) {
  92. /**
  93. * if (existingIssue.getIssueName().equals(newIssue.getIssueName()))
  94. * return -1;
  95. * else return 0;
  96. */
  97. return 0;
  98. }
  99. //JTable使用此方法来确定它应该显示多少行
  100. @Override
  101. public int getRowCount() {
  102. return this.log.size();
  103. }
  104. //JTable使用此方法来确定默认情况下它应该创建和显示多少列
  105. @Override
  106. public int getColumnCount() {
  107. return 3;
  108. }
  109. //返回列的默认名称
  110. @Override
  111. public String getColumnName(int columnIndex) {
  112. switch (columnIndex) {
  113. case 0:
  114. return "Vuln";
  115. case 1:
  116. return "URL";
  117. case 2:
  118. return "Position";
  119. }
  120. return "";
  121. }
  122. @Override
  123. public Class<?> getColumnClass(int columnIndex) {
  124. return String.class;
  125. }
  126. //返回值
  127. @Override
  128. public Object getValueAt(int rowIndex, int columnIndex) {
  129. LogEntry logEntry = this.log.get(rowIndex);
  130. switch (columnIndex) {
  131. case 0:
  132. return "SpringCoreRCE";
  133. case 1:
  134. return logEntry.url.toString();
  135. case 2:
  136. return logEntry.position;
  137. }
  138. return "";
  139. }
  140. //设置Tab标签名称
  141. @Override
  142. public String getTabCaption() {
  143. return "YanMu";
  144. }
  145. //设置Tab标签的内容
  146. @Override
  147. public Component getUiComponent() {
  148. return this.splitPane;
  149. }
  150. //返回相关request信息
  151. @Override
  152. public byte[] getRequest() {
  153. return this.currentlyDisplayedItem.getRequest();
  154. }
  155. //返回相关response信息
  156. @Override
  157. public byte[] getResponse() {
  158. return this.currentlyDisplayedItem.getResponse();
  159. }
  160. //返回相关HttpService信息
  161. @Override
  162. public IHttpService getHttpService() {
  163. return this.currentlyDisplayedItem.getHttpService();
  164. }
  165. private class Table extends JTable {
  166. public Table(TableModel tableModel) {
  167. super(tableModel);
  168. }
  169. //更新表格
  170. @Override
  171. public void changeSelection(int row, int col, boolean toggle, boolean extend) {
  172. //返回此列表中指定位置的元素
  173. BurpExtender.LogEntry logEntry = BurpExtender.this.log.get(row);
  174. BurpExtender.this.requestViewer.setMessage(logEntry.requestResponse.getRequest(), true);
  175. BurpExtender.this.responseViewer.setMessage(logEntry.requestResponse.getResponse(), false);
  176. BurpExtender.this.currentlyDisplayedItem = logEntry.requestResponse;
  177. super.changeSelection(row, col, toggle, extend);
  178. }
  179. }
  180. //检测变化
  181. private static class LogEntry {
  182. final int tool;
  183. final IHttpRequestResponse requestResponse;
  184. final URL url;
  185. final String position;
  186. LogEntry(int tool, IHttpRequestResponse requestResponse, URL url, String position) {
  187. this.tool = tool;
  188. this.requestResponse = requestResponse;
  189. this.url = url;
  190. this.position = position;
  191. }
  192. }
  193. }

image.png

DNS

被动扫描DNSlog的出场率一直很高

burp自带的DNSlog
  1. //与dnslog平台进行交互
  2. IBurpCollaboratorClientContext context= this.callbacks.createBurpCollaboratorClientContext();
  3. // 一个burp提供的dnslog平台
  4. String dnslog = context.generatePayload(true);
  5. List<IBurpCollaboratorInteraction> dnsres = new ArrayList<>();
  6. this.stdout.println(dnslog);
  7. //替换以后String.format(),发送请求
  8. IHttpRequestResponse resp = this.callbacks.makeHttpRequest(iHttpService, new_Request);
  9. //休眠2秒
  10. Thread.sleep(2000);
  11. // 返回的是一个dns响应数组
  12. dnsres = context.fetchCollaboratorInteractionsFor(dnslog);
  13. this.stdout.println(dnsres);
  14. if(!dnsres.isEmpty()){
  15. this.stdout.println("found!!!");
  16. // 漏洞存在就更新表格中存在漏洞那一行的数据
  17. //LogEntry logEntry = new LogEntry(url, "finished", "vul!!!", resp);
  18. //log.set(row, logEntry);
  19. // 这个方法是swing中的一个方法,会通知表格更新指定行的数据
  20. //fireTableRowsUpdated(row, row);
  21. break;
  22. }

sprigcore为演示

解释基本都在注释中

  1. package burp;
  2. import java.awt.Component;
  3. import java.io.PrintWriter;
  4. import java.net.URL;
  5. import java.util.ArrayList;
  6. import java.util.List;
  7. import java.util.Random;
  8. //import java.util.Random;
  9. import javax.swing.JScrollPane;
  10. import javax.swing.JSplitPane;
  11. import javax.swing.JTabbedPane;
  12. import javax.swing.JTable;
  13. import javax.swing.SwingUtilities;
  14. import javax.swing.table.AbstractTableModel;
  15. import javax.swing.table.TableModel;
  16. //IMessageEditor使用IMessageEditorController接口获取当前显示的消息的详细信息
  17. //扩展可以实现这个IScannerCheck接口,然后调用 IBurpExtenderCallbacks.registerScannerCheck()来注册一个自定义的 Scanner 检查。
  18. //Itab这个接口用于使用 IBurpExtenderCallbacks.addSuiteTab ()之类的方法为 Burp 提供将添加到 Burp 的 UI 中的自定义选项卡的详细信息。
  19. public class BurpExtender extends AbstractTableModel implements IBurpExtender, IScannerCheck, ITab, IMessageEditorController {
  20. //IBurpExtenderCallbacks接口类是IBurpExtender接口的实 现类与Burp其他各个组件(Scanner、Intruder、Spider......)、各个通信对象 (HttpRequestResponse、HttpService、SessionHandlingAction)之间的连接
  21. private IBurpExtenderCallbacks callbacks;
  22. //IExtensionHelpers接口包含许多辅助方法,扩展可以使用这些方法来协助 Burp 扩展出现的各种常见任务
  23. //比如向 HTTP 请求添加新参数,分析 HTTP 请求
  24. private IExtensionHelpers helpers;
  25. //分隔面板,SplitPane用于分隔两个(只能两个)组件,分割request和response
  26. private JSplitPane splitPane;
  27. //IMessageEditor用于为扩展提供 Burp 的 HTTP 消息编辑器的实例,以便扩展在其自己的 UI 中使用
  28. private IMessageEditor requestViewer;
  29. private IMessageEditor responseViewer;
  30. private final List<LogEntry> log = new ArrayList<>();
  31. //IHttpRequestResponse此接口用于检索和更新有关 HTTP 消息的详细信息。
  32. private IHttpRequestResponse currentlyDisplayedItem;
  33. private String ExtenderName = "SpringCore";
  34. private PrintWriter stdout;
  35. //此方法将在扩展加载后被调用,它注册了一个 IBurpExtenderCallbacks 接口的实例, IBurpExtenderCallbacks 接口提供了许多在开发插件过程中常用的一些操作。
  36. @Override
  37. public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) {
  38. //获取burp提供的标准输出流
  39. this.stdout = new PrintWriter(callbacks.getStdout(), true);
  40. //打印到标准输出流
  41. this.stdout.println(this.ExtenderName);
  42. //保留对callbacks的引用
  43. this.callbacks = callbacks;
  44. //此方法用于获取IExtensionHelpers对象,扩展程序可以使用该对象执行许多有用的任务。
  45. this.helpers = callbacks.getHelpers();
  46. //设置插件的名称
  47. callbacks.setExtensionName(this.ExtenderName);
  48. //调用 IBurpExtenderCallbacks.registerScannerCheck()来注册一个自定义的 Scanner 检查
  49. callbacks.registerScannerCheck(this);
  50. //创建一个Swing线程用于我们的burp的UI
  51. SwingUtilities.invokeLater(new Runnable() {
  52. @Override
  53. public void run() {
  54. BurpExtender.this.splitPane = new JSplitPane(0);
  55. BurpExtender.Table logTable = new BurpExtender.Table(BurpExtender.this);
  56. //创建一个滚动条
  57. JScrollPane scrollPane = new JScrollPane(logTable);
  58. //设置滚动条位置左侧或上方
  59. BurpExtender.this.splitPane.setLeftComponent(scrollPane);
  60. JTabbedPane tabs = new JTabbedPane();
  61. //此方法用于创建 Burp 的 HTTP 消息编辑器的新实例,以便扩展在其自己的 UI 中使用。
  62. BurpExtender.this.requestViewer = callbacks.createMessageEditor(BurpExtender.this, false);
  63. BurpExtender.this.responseViewer = callbacks.createMessageEditor(BurpExtender.this, false);
  64. tabs.addTab("Request", BurpExtender.this.requestViewer.getComponent());
  65. tabs.addTab("Response", BurpExtender.this.responseViewer.getComponent());
  66. //将组件设置在分隔线的右侧或下方
  67. BurpExtender.this.splitPane.setRightComponent(tabs);
  68. //customizeUiComponent方法用于自定义符合 Burp 的 UI 风格的 UI 组件,包括字体大小、颜色、表格行距等。
  69. callbacks.customizeUiComponent(BurpExtender.this.splitPane);
  70. callbacks.customizeUiComponent(logTable);
  71. callbacks.customizeUiComponent(scrollPane);
  72. callbacks.customizeUiComponent(tabs);
  73. //addSuiteTab方法用于将自定义选项卡添加到 Burp Suite 主窗口。
  74. callbacks.addSuiteTab(BurpExtender.this);
  75. }
  76. });
  77. }
  78. //设置我们的被动扫描
  79. @Override
  80. public List<IScanIssue> doPassiveScan(IHttpRequestResponse baseRequestResponse) {
  81. //IHttpRequestResponse接口用于检索和更新有关 HTTP 消息的详细信息
  82. try {
  83. //此方法可用于分析 HTTP 请求,并获取有关它的各种关键细节。
  84. IRequestInfo analyzeRequest = this.helpers.analyzeRequest(baseRequestResponse);
  85. //此方法用于检索请求消息。
  86. byte[] request = baseRequestResponse.getRequest();
  87. //此方法用于检索此请求/响应的 HTTP 服务。
  88. IHttpService httpService = baseRequestResponse.getHttpService();
  89. boolean flag = false;
  90. URL url = analyzeRequest.getUrl();
  91. String subfix = getSubfix(url.toString());
  92. //设置不扫描的路径
  93. String blacklist = ".js|.png|.jpg|.jpeg|.svg|.gif|.ico|.css";
  94. if (!subfix.isEmpty() && blacklist.contains(subfix)) {
  95. this.stdout.println(subfix + " no check");
  96. return null;
  97. }
  98. String host = httpService.getHost();
  99. int port = httpService.getPort();
  100. String protocol = httpService.getProtocol();
  101. boolean https = false;
  102. if (protocol.equals("https")){
  103. https = true;
  104. }
  105. List<IParameter> paramsList = analyzeRequest.getParameters();
  106. byte getCode = 0;
  107. byte postCode = 1;
  108. this.stdout.println("checking..." + analyzeRequest.getUrl());
  109. //此方法根据提供的详细信息构造一个 IParameter 对象。
  110. IParameter getParam = this.helpers.buildParameter("class.mod%75le.classLoader.DefaultAssertionStatus", "true", getCode);
  111. IParameter postParam = this.helpers.buildParameter("class.mod%75le.classLoader.DefaultAssertionStatus", "true", postCode);
  112. request = this.helpers.updateParameter(request, getParam);
  113. //此方法更新 HTTP 请求中的参数值,如果适当,则更新 Content-Length 标头。
  114. request = this.helpers.updateParameter(request, postParam);
  115. //此方法可用于发出 HTTP 请求并检索其响应。
  116. IHttpRequestResponse newRequest2 = this.callbacks.makeHttpRequest(httpService, request);
  117. //此方法可用于分析 HTTP 响应,并获取关于它的各种关键细节。
  118. IResponseInfo analyzeRequest2 = this.helpers.analyzeResponse(newRequest2.getResponse());
  119. if (analyzeRequest2.getStatusCode() == 200){
  120. flag = true;
  121. }
  122. if (flag) {
  123. //制造
  124. IParameter getParam2 = this.helpers.buildParameter("class.mod%75le.classLoader.DefaultAssertionStatus", createRandomStr(4), getCode);
  125. IParameter postParam2 = this.helpers.buildParameter("class.mod%75le.classLoader.DefaultAssertionStatus", createRandomStr(4), postCode);
  126. //更新请求参数
  127. request = this.helpers.updateParameter(request, getParam2);
  128. request = this.helpers.updateParameter(request, postParam2);
  129. IHttpRequestResponse newRequest3 = this.callbacks.makeHttpRequest(httpService, request);
  130. IResponseInfo analyzeRequest3 = this.helpers.analyzeResponse(newRequest3.getResponse());
  131. if (analyzeRequest3.getStatusCode() == 400){
  132. synchronized (this.log) {
  133. int row = this.log.size();
  134. //漏洞利用成功返回表格数据
  135. this.log.add(new LogEntry(4, this.callbacks.saveBuffersToTempFiles(newRequest3), this.helpers.analyzeRequest(baseRequestResponse).getUrl(), null));
  136. fireTableRowsInserted(row, row);
  137. }
  138. }
  139. }
  140. } catch (Exception e) {
  141. this.stdout.print(e);
  142. return null;
  143. }
  144. return null;
  145. }
  146. //Url解析
  147. public String getSubfix(String url) {
  148. int index = url.indexOf("?");
  149. String tmp = "";
  150. if (index != -1) {
  151. tmp = url.substring(0, index);
  152. } else {
  153. tmp = url;
  154. }
  155. int index2 = tmp.lastIndexOf("/");
  156. String tmp2 = tmp.substring(index2 + 1);
  157. int index3 = tmp2.lastIndexOf(".");
  158. return tmp2.substring(index3 + 1);
  159. }
  160. //设置我们的随机数
  161. public String createRandomStr(int length) {
  162. String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  163. Random random = new Random();
  164. StringBuffer stringBuffer = new StringBuffer();
  165. for (int i = 0; i < length; i++) {
  166. int number = random.nextInt(62);
  167. stringBuffer.append(str.charAt(number));
  168. }
  169. return stringBuffer.toString();
  170. }
  171. //设置我们的主动扫描
  172. @Override
  173. public List<IScanIssue> doActiveScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) {
  174. return null;
  175. }
  176. //当报告同一URL有多个问题时,将调用此方法
  177. //漏洞去重函数,可以根据自己的需求,根据不同的参数来判断是否重复,比如URL、漏洞名称等
  178. @Override
  179. public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue) {
  180. /**
  181. * if (existingIssue.getIssueName().equals(newIssue.getIssueName()))
  182. * return -1;
  183. * else return 0;
  184. */
  185. return 0;
  186. }
  187. //JTable使用此方法来确定它应该显示多少行
  188. @Override
  189. public int getRowCount() {
  190. return this.log.size();
  191. }
  192. //JTable使用此方法来确定默认情况下它应该创建和显示多少列
  193. @Override
  194. public int getColumnCount() {
  195. return 3;
  196. }
  197. //返回列的默认名称
  198. @Override
  199. public String getColumnName(int columnIndex) {
  200. switch (columnIndex) {
  201. case 0:
  202. return "Vuln";
  203. case 1:
  204. return "URL";
  205. case 2:
  206. return "Position";
  207. }
  208. return "";
  209. }
  210. @Override
  211. public Class<?> getColumnClass(int columnIndex) {
  212. return String.class;
  213. }
  214. //返回值
  215. @Override
  216. public Object getValueAt(int rowIndex, int columnIndex) {
  217. LogEntry logEntry = this.log.get(rowIndex);
  218. switch (columnIndex) {
  219. case 0:
  220. return "SpringCoreRCE";
  221. case 1:
  222. return logEntry.url.toString();
  223. case 2:
  224. return logEntry.position;
  225. }
  226. return "";
  227. }
  228. //设置Tab标签名称
  229. @Override
  230. public String getTabCaption() {
  231. return this.ExtenderName;
  232. }
  233. //设置Tab标签的内容
  234. @Override
  235. public Component getUiComponent() {
  236. return this.splitPane;
  237. }
  238. //返回相关request信息
  239. @Override
  240. public byte[] getRequest() {
  241. return this.currentlyDisplayedItem.getRequest();
  242. }
  243. //返回相关response信息
  244. @Override
  245. public byte[] getResponse() {
  246. return this.currentlyDisplayedItem.getResponse();
  247. }
  248. //返回相关HttpService信息
  249. @Override
  250. public IHttpService getHttpService() {
  251. return this.currentlyDisplayedItem.getHttpService();
  252. }
  253. private class Table extends JTable {
  254. public Table(TableModel tableModel) {
  255. super(tableModel);
  256. }
  257. //更新表格
  258. @Override
  259. public void changeSelection(int row, int col, boolean toggle, boolean extend) {
  260. //返回此列表中指定位置的元素
  261. BurpExtender.LogEntry logEntry = BurpExtender.this.log.get(row);
  262. BurpExtender.this.requestViewer.setMessage(logEntry.requestResponse.getRequest(), true);
  263. BurpExtender.this.responseViewer.setMessage(logEntry.requestResponse.getResponse(), false);
  264. BurpExtender.this.currentlyDisplayedItem = logEntry.requestResponse;
  265. super.changeSelection(row, col, toggle, extend);
  266. }
  267. }
  268. //检测变化
  269. private static class LogEntry {
  270. final int tool;
  271. final IHttpRequestResponse requestResponse;
  272. final URL url;
  273. final String position;
  274. LogEntry(int tool, IHttpRequestResponse requestResponse, URL url, String position) {
  275. this.tool = tool;
  276. this.requestResponse = requestResponse;
  277. this.url = url;
  278. this.position = position;
  279. }
  280. }
  281. }

image.png
image.png