Introduction to Web Assembly for Java engineers

Introduction

The goal of this ticket is to present the different technological components of WebAssembly  in comparison with the Java technological stack.

Why comparing WebAssembly with Java ? I think that WebAssembly have more chances to succeed in achieving the slogan “Write once, run anywhere” that have been coined  more than 25 years ago to illustrate the cross-platform benefits of the Java language.

WebAssembly is a standard that contains a virtual Instruction Set Architecture (ISA) for a stack machine. WebAssembly is designed to run on a virtual machine. The virtual machine allows WebAssembly to run on a variety of computer hardware and digital devices but today the most common way to execute WebAssembly code is from browsers.

In a nutshell the comparison will be done using the following points of interest and the next image is summarizing this:

  • Executable Code
  • Programming Languages
  • ToolChains/Compilers
  • Execution Environment

Executable Code

Both technologies, WebAssembly and Java have the notion of executable code.

In Java this is called bytecode and is part of the JVM specification, see See Chapter 4. The class File Format and Chapter 6. The Java Virtual Machine Instruction Set of the The Java Virtual Machine Specification.

In WebAssembly this is called WASM. Actually there are 2 formats; a binary format and a text, human readable format called WAT (WebAssembly Text).

Java bytecode and WebAssembly (WASM) are both low-level, platform-independent binary formats but there are some notable differences:

  • Java bytecode is strongly typed. It has a well-defined type system that enforces type safety. WebAssembly is designed with a more loosely typed system. It operates on a set of basic value types, including integers, floats, and vectors.
  • Java bytecode has built-in support for object-oriented programming features, including classes, interfaces, and inheritance. WebAssembly is more low-level compared to Java bytecode and lacks the rich type system found in Java bytecode.
  • Java bytecode runs in the Java Virtual Machine (JVM), which manages memory automatically, including garbage collection. WebAssembly provides a linear memory model, which is essentially a resizable array of bytes. It allows more direct memory access and manipulation.
  • The JVM abstracts the memory management, making it relatively opaque to developers. In WebAssembly the developers have explicit control over memory allocation and deallocation making it potentially more error-prone.

Programming Language

To develop applications, Java developers have to use the Java language. In contrast, WebAssembly is intentionally crafted to serve as a versatile and language-agnostic platform suitable for a broad spectrum of programming languages.

WebAssembly supports an array of programming languages, including but not limited to C/C++, R, TypeScript (using the AssemblyScript language), Scala, Kotlin, and even Java.

Furthermore,WebAssembly offers a human-readable text format known as WAT. It is designed to be a more readable and writable representation of WebAssembly code compared to the binary format.

ToolChains/Compilers

In order to transform the Java source code into bytecode, the Java developers are using a compiler. The WebAssembly have a similar concept; compilers or toolchains to transform the source code into wasm. Here are a few examples of toolschains:

  • wat2wasm – a command-line tool provided by the WABT (WebAssembly Binary Toolkit)  and its purpose is to convert WebAssembly Text Format code to the binary WebAssembly format (Wasm). The WAT also includes an wasm2wat tool which converts Wasm to Wat.
  • emscriptem – an open-source compiler toolchain that translates C and C++ code into WebAssembly (Wasm) or JavaScript.
  • wasm-pack – to generate WebAssembly from Rust language.
  • AssemblyScript – is a subset of TypeScript specifically designed for WebAssembly.
  • TeaVM – an ahead-of-time compiler for Java bytecode that emits JavaScript and WebAssembly that runs in a browser. Moreover, the source code is not required to be Java, so TeaVM successfully compiles Kotlin and Scala.

Execution Environment

In the Java case the execution environment is the Java Virtual Machine.In the case of WebAssembly there are multiple ways to execute an application.

The initial execution environment for which  WebAssembly was created is the browser. All the modern browsers are offering support for WebAssembly execution; the execution performance is near-native.

Running WebAssembly on browsers have a few constraints:

  • WebAssembly runs in a sandboxed environment within the browser for security reasons. While this is generally beneficial, it also imposes restrictions on certain operations, such as direct access to the DOM or file system. Interactions with the browser environment are typically done through JavaScript
  • WebAssembly modules cannot directly access browser APIs. Interactions with the DOM, events, and other browser features are typically done through JavaScript, requiring careful coordination between the two.
  • Browsers impose memory constraints on WebAssembly applications to ensure a secure and stable user experience. The memory allocated to a WebAssembly module is limited, and exceeding these limits can result in termination of the module.
  • Loading and parsing WebAssembly modules can take time, especially for larger applications. The initial loading time may be impacted, affecting the user experience.

Node.js has support for WebAssembly on the server side through the wasm module. This module allows you to load and interact with WebAssembly modules directly in your Node.js applications.

Last but not least, the WebAssembly Working Group, which is a part of the World Wide Web Consortium (W3C) created WebAssembly System Interface (WASI). The goal of WASI is to provide a standardized set of interfaces that allows WebAssembly modules to interact with the host environment in a secure, and platform-independent manner.

WASI defines a system interface that includes a set of system calls, similar to traditional operating system interfaces. The standard also provides a sandboxed execution environment for WebAssembly modules, ensuring that they have limited and controlled access to the host system.

WASI aims to be platform-independent, allowing WebAssembly modules to run on different operating systems without modification. This is achieved by defining a standardized set of system calls that abstract away the specifics of the underlying host system.

Various WebAssembly execution environments, also known as runtimes, are incorporating the WebAssembly System Interface (WASI). Notable examples include wasmtime, a standalone WebAssembly runtime developed by the Bytecode Alliance; lucet-WASI, a high-performance WebAssembly compiler and runtime created by Fastly; and  wasi-libc, serving as the WASI Reference Implementation.

It’s worth mentioning that Docker started implementing WASI last year, enabling native execution of WebAssembly (wasm) files. For additional information, you can refer to the details provided in the announcement of Docker+Wasm Technical Preview 2.

Lessons learned from using Jenkins on containers a.k.a CloudBees CI

Recently, I encountered some issues with a (Jenkins) declarative pipeline running on CloudBees CI, specifically for a Python project. The CloudBees CI instance was operating within an OpenShift Platform (OCP) cluster, and I was employing the Kubernetes plugin for managing agents/slaves.

Here are the lessons learned, some of them linked to OCP/K8S, some linked to CloudBees CI, some linked to Python and some other linked to all this technologies put together:

  1. Make sure that the application packaged as image is running properly when executed from a plain Docker/Podman system.
  2. The container/s will be run into a pod so the (Dockerfile) WORKDIR instruction (if defined) in the container/s will be ignored. If you need to define a working directory then you should specify this into the pod definition via pod.spec.template.spec.containers.workingDir.
  3. The default working directory for the running container/s will be (Jenkins) ${env.WORKSPACE}.
  4. If the pipeline code is fetched from a Git repository then the repository will be automatically mapped as volume inside the container/s under the folder ${env.WORKSPACE}
  5. For container/s running Python the sys.path variable will be: ‘ ‘, ${env.WORKSPACE}, container.PYTHONPATH  in this specific order, where ‘ ‘ is the current directory which by default will be ${env.WORKSPACE} (see point number 3). So the default sys.path will be: ${env.WORKSPACE}, ${env.WORKSPACE}, container.PYTHONPATH 
  6. The (nasty) side effect of the previous point is that if there are Python modules in Git repository (which is mounted in the container, see point number 4) and in the container/s, having the same name, then the module/s from the Git repository will be used for execution and not the one from container/s.
  7. If you want to revert the situation from previous point the only way is to play on the first element of the sys.path variable which will always be the current directory (‘ ‘). If the first element of the sys.path is / then the container/s modules will be used for execution instead of the Git repository modules.
  8. The sys.path variable is computed at runtime by the Python interpreter so it cannot be modified in advance (like the PYTHONPATH environment variable) prior to the execution of a program.  

 

How to properly use (Java) Text Blocks with String.format

Introduction

As of Java 15 there is a new feature called Text Block (also sometimes called Multi-Line Strings). The Text Blocks can be used by declaring the string with “””:

String multiline = """
                line1
                line2
                """;

Since Java 1.5 the String class have a format method.Java’s String.format() is a static method that returns a formatted String using the given locale, format String, and arguments.

Problem

It is a bad practice (see SpotBugs FS: Format string should use %n rather than \n) to use platform specific <EOL>character/s within strings to be formatted. For example if your string to be formatted contains Linux EOL character (\n) it might be wrongly interpreted if the code is executed on Windows platform on which the EOL character is \r\n.

In format strings, it is generally preferable to use %n, which will produce the platform-specific line separator at runtime.

Now, the Text Blocks will have multiple lines so what is the right way to still use multi-line strings and have a portable format strings ?

Solution

  • use %n format specifier to represent a newline character
  • use \ escape character so that the new lines inserted by the IDE are ignored.The \<line-terminator> escape sequence explicitly suppresses the inclusion of an implicit new line character.
 String multiline = """
                line1%n\
                line2%n\
                """;

Book Review: Antivirus Bypass Techniques

This is the review of Antivirus Bypass Techniques book.

(My) Conclusion

This book is a niche subject book, to be more precise it’s about a niche tool (antivirus) used in a niche domain (endpoint security) of cybersecurity.

As his name implies, it describes how antivirus products are working and different technique to evade this antivirus products.

The book it’is not very technical (compared with a programming book) but it implies some knowledge of Windows OS architecture, Assembler and Python.

If you are new to this subject (like myself) it’s very good introduction that will give you a rather technical glimpse of the mouse and cat “game” played between the antivirus developers and malware creators.

If you are already working in the endpoint security domain you (probably) already know all the techniques presented in the book.

1.Introduction to the Security Landscape

This chapter is exploring the following topics:

  • definition of different malware types:
    • Virus: A malware type that replicates itself in the system.
    • Worm: A type of malware whose purpose is to spread throughout a network and
      infect computers connected to that network.
    • Rootkit: A type of malware that is found in lower levels of the operating system that
      tend to be highly privileged.
    • Downloader: A type of malware whose function is to download and run from the
      internet some other malicious files.
    • Ransomware: A type of malware whose purpose is to encrypt computer files and
      demand financial ransom from the user before they can access their files.
    • Botnet: Botnet malware causes the user to be a small part of a large network of
      infected computers.
    • Backdoor: A type of malware whose purpose is  to leave open a “back door”, providing the attacker with ongoing access to the user’s computer.
    • PUP: An acronym that stands for potentially unwanted program, a name that
      includes malware whose purpose is to present undesirable content to the user, for
      instance, ads.
    • Dropper: A type of malware whose purpose is to “drop” a component of itself into
      the hard drive.
    • Scareware: A type of malware that presents false data about the computer it is
      installed on, so as to frighten the user into performing actions that could be
      malicious, such as installing fake antivirus software or even paying money for it.
    • Trojan: A type of malware that performs as if it were a legitimate, innocent
      application within the operating system.
    • Spyware: A type of malware whose purpose is to spy on the user and steal their
      information to sell it for financial gain.
  • definition of different protection system types:
    • EDR (Endpoint Detection and Response): The purpose of EDR systems is to protect the business user from malware
      attacks through real-time response to any type of event defined as malicious.
    • Firewall: A system for monitoring, blocking, and identification of network-based
      threats, based on a pre-defined policy.
    • IDS/IPS (Intrusion Detection and Protection System): IDS and IPS provide network-level security, based on generic signatures, which inspects network packets and searches for malicious patterns or malicious flow.
    • DLP (Data Loss Prevention): DLP’s sole purpose is to stop and report on sensitive data exfiltrated from the organization, whether on portable media (thumb drive/disk on key), email, uploading to a file server, or more.
  • the basics of an antivirus product. Most of the antivirus products have different types of engines:
    • static engine: Conducts comparisons of existing files within the operating system against a database of signatures, and in this way can identify malware.
    • dynamic engine: Is checking the files at runtime using API monitoring (the goal of API monitoring is to intercept API calls in the operating system and to detect the malicious ones) and sand-boxing (A sandbox is a virtual environment that is separated from the memory of the physical host computer. This allows the detection and analysis of malicious software by executing it within a virtual environment)
    • heuristic engine: This type of engine determines a score for each file by conducting a statistical analysis that combines the static and dynamic engine methodologies.
    • unpacker engine: Unpacking is the process of restoring the original malware code; the malicious code was “packed” in order to hide a malicious patterns and thus thwart
      signature-based detection. The unpacker engine is able to detect if a file contains a (known) unpacker code.

2.Before Research Begins

In order to evade the antivirus products you must have a good understanding about how the different antivirus program components are working. The authors are using different tools (on Windows OS only) that usually are used for malware analysis to discover the working mechanics of the AVG Antivirus.

The authors are using the following tools :

  • Process Explorer is a tool that will will provide us with a lot of relevant information about the processes that are running in the operating system like the file name of the processes, the percentage of the CPU resources for the processes, the amount of memory and RAM allocated to the processes. Using the Process Explorer it is possible for example to find the hook that are used by the antivirus software to conduct monitoring on every process that exists within the operating system. This hook is usually a DLL file that is injected into every process running within the operating system
  • Process Monitor is a tool that can be used to observe the behavior of each process in the operating system from the moment when are started until the moment are closed. Using the Process Monitor is possible for example to find the processes used by the antivirus software for specific tasks like scanning a specific file.
  • Autoruns is a tool that shows what programs are configured to run during system bootup or login, and when you start various built-in Windows applications. With Autoruns is possible to use filters to find for example all the antivirus software files that are loaded at the startup of the operating system.
  • Regshot is an open source tool that lets you take a snapshot of your registry, then compare two registry shots, before and after installing a program. In this case it is used to find all the registry changes that took place after installing the antivirus software

3.Antivirus Research Approaches

The authors are proposing two methods to bypass the  antivirus software:

  • Find and exploit a vulnerability in the antivirus software
  • Find and use a detection bypass method

This chapter gives a few details about the first method; basically it presents a few vulnerabilities on different antivirus software packages that had impact on the way the antivirus was functioning:

  • Insufficient permissions on the static signature file. The file containing static signature had insufficient permissions meaning that any low-privileged user could modify the content of the file.
  • Unquoted service path. When a service is created within the Windows operating system and the executable path contains spaces and the path is not enclosed within quotation marks, the service will be susceptible to an Unquoted Service Path vulnerability.
    To exploit this vulnerability, an executable file must be created in a particular location in the service’s executable path, and instead of starting up the antivirus service, the service we created previously will load first and cause the antivirus to not load during operating system startup
  • DLL hijaking. When software wants to load a particular DLL, it uses the LoadLibraryW() Windows API call. It passes as a parameter to this function the name of the DLL it wishes to load. It is not recommended to use the LoadLibrary() function, due to the fact that it is
    possible to replace the original DLL with another one that has the same name, and in that
    way to cause the program to run our DLL instead of the originally intended DLL.

If you are interested of other types of vulnerabilities linked to the antivirus products you can look into the CVE MITRE database using the keyword antivirus.

4.Bypassing the Dynamic Engine

As explained in the first chapter the dynamic engine is checking the runtime behavior of files using API monitoring and sand-boxing. The authors are presenting two types of techniques for bypassing the dynamic engine:

 Bypass using process Injection

Process injection goal is to inject a piece of code into the process memory address space of another process, give this memory address space
execution permissions, and then execute the injected code. The general steps of a process injection are:

  1. Identify a target process.
  2. Receive a handle for the targeted process to access its process address space.
  3. Allocate a virtual memory address space where the code will be injected and
    executed, and assign an execution flag if needed.
  4. Perform code injection into the allocated memory address space of the targeted
    process.
  5. Execute the injected code.

The authors are presenting three process injections techniques; there are a lot more techniques, for a non-exhaustive list you can check MITRE Pricess Injection Techniques :

  • DLL Injection DLL injection is commonly performed by writing the path to a DLL in the virtual address space of the target process before loading the DLL by invoking a new thread. The write can be performed with native Windows API calls such as VirtualAllocEx and WriteProcessMemory, then invoked with CreateRemoteThread (which calls the LoadLibrary API responsible for loading the DLL).
  • Process hollowing Process hollowing is commonly performed by creating a process in a suspended state then unmapping/hollowing its memory, which can then be replaced with malicious code.
  • Process doppelganging This technique is using the Windows Transactional NTFS (TxF) API. TxF was introduced in Vista as a method to perform safe file operations.To ensure data integrity, TxF enables only one transacted handle to write to a file at a given time. Until the write handle transaction is terminated, all other handles are isolated from the writer and may only read the committed version of the file that existed at the time the handle was opened. Adversaries may abuse TxF for replacing the memory of a legitimate process, enabling the veiled execution of malicious code.

Bypass using timing-based techniques

Timing based techniques are based on the fact that antivirus vendors prefer to scan about 100,000 files in 24 minutes, with a detection rate of about 70%, over scanning the same number of files in 24 hours, with a detection rate of around 95%.

The first technique will utilize Windows API calls that cause the delay of the malware functionality, so the dynamic engine will not be able to spot the malware because it is not executed in a timely manner. A basic technique to  implement this behavior is by using the sleep() function combined with the GetTickCount() function.

The usage of the sleep() function only can be detected by the antivirus static engine and then antivirus emulator (used by dynamic engine) will simulate the pass of the sleep time thus bypassing the malware defense mechanism. The  usage of GetTickCount() (which returns the amount of time the operating system has been up and running) will counter this (time forward) emulation because the malware will be able to detect it.

The second technique is named by the authors the memory bombing and take advantage of the limited time that antivirus software has to dedicate to each individual file during scanning.

The pseudo-code for this technique looks like:

int main(){
char *memory_bombing = NULL;

//Initialize the memory_bombing variable with a bunch of
//zeroes.
//At this point, the antivirus is struggling to scan the
//file and forfeits

memory_bombing = (char *) calloc(200000000, sizeof(char));

if(memory_bombing != NULL) {
//free the memory allocated to memory_bombing
free(memory_bombing);
payload();
}
return 0;
}

The logic behind this type of bypass technique relies on the dynamic antivirus engine
scanning for malicious code in newly spawned processes by allocating virtual memory so
that the executed process can be scanned for malicious code in a sandboxed environment.
The allocated memory is limited because antivirus engines do not want to impact the user
experience so if the antivirus engine have to allocate a large amount of memory the antivirus engines will not scan the file.

5.Bypassing the Static Engine

The static engine is using file signatures to spot malicious files so, a lot of antiv-viruses are embedding the YARA tool; the chapter contains also a small introduction to YARA templates.

There are three ways to by pass the static engine:

    • Code Obfuscation is the process of making applications difficult or impossible to de-compile or disassemble, and make the application code more difficult to parse. The code obfuscation could defeat the ARA templates which are looking for specific strings into the files.
    • Encryption In this case the malicious functionality of the malware will be encrypted and appear as a harmless piece of code , meaning the antivirus software will treat it as such and will allow the malware to successfully run on the system.
      But before malware starts to execute its malicious functionality, it needs to decrypt its
      code within runtime memory. Only after the malware decrypts itself will the code be
      ready to begin its malicious actions. There are different encryption techniques used by the malwares:
      • Oligomorphic code includes several decryptors that malware can use. Each time it runs on the system, it randomly chooses a different decryptor to decrypt itself.
      • Polymorphic code mostly uses a polymorphic engine that usually has two roles. The first role is choosing which decryptor to use, and the second role is loading the relevant source code so that the encrypted code will match the selected decryptor.
      • Metamorphic code is code whose goal is to change the content of malware each time it runs, thus causing itself to mutate.
    • Packing A packer is a tool used to mask a malicious file. In general, packers work by taking an EXE file and obfuscating and compressing the code section (“.text” section) using a predefined algorithm. Following this, packers add a region in the file referred to as a stub, whose purpose is to unpack the software or malware in the operating system’s runtime memory and transfer the execution to the original entry point (OEP). The OEP is the entry point that was originally defined as the start of program execution before packing took place.The authors are presenting how the UPX and AsPAck packers are packaging a file and how unpacker will have to work in order to detect the content of the original file.

6.Other Antivirus Bypass Techniques

This chapter presents other bypass techniques:

  • Binary patching: It consists in opening/executing a binary through a debugger (the x32dbg/x64dbg in the book example), changing (on the fly) some code and then re-generating a new binary using the “Patch File” functionality of the debugger. This technique would defeat the static engine.
  • Timestomping; It consists in changing some metadata of a binary file like the created date. This relies on the fact that the creation date could be used for computing static signatures of different files so changing the creation date could defeat a static engine.
  • Junk code. Technique very similar to the Code Obfuscation technique presented in the chapter 5 Bypassing the static engine. The Junk code technique could also add empty functions, or loading non-existing files that could confuse the dynamic engine.
  • PowerShell. It consists in executing a payload directly from powershell; The powershell binary being a trusted file then the dynamic engine might be bypassed. 
  • Single malicious functionality If the static and the dynamic engine are not able to decide if a file is malicious then the heuristic engine will try to compute a score for the scanned file. The heuristic engine have a detection threshold under which a scanned file will not be marked as malicious even if it contains some potentially malicious components. The goal for the malware developer/s is to find the maximum number of malicious actions that will be under the detection threshold of the heuristic engine. 

7.Antivirus Bypass Techniques in Red Team Operations

This chapter is for me rather badly named; it starts by explaining what are the responsibilities and the goals of a red team and how it use the techniques presented into this book in the context of pen tests.

But, the main part if the chapter presents how a malware can check what antivirus products are installed on the endpoints that it wants to attack in order to apply the right bypass techniques, action that the authors are calling the fingerprinting of the antivirus software.

Antivirus fingerprinting can be done based on identifiable constants, such as the following:  Service names (for example, WinDefend is for example the service name for Microsoft Defender), Process names (or example, AVGSvc.exe is the process name of the AVG antivirus), Domain names, Registry keys or Filesystem artifacts.

The authors are recommending the following GitHub repository ethereal-vx/Antivirus-Artifacts  to find more details about Antivirus fingerprinting

8.Best Practices and Recommendations

The last chapter of the book can be split in 2 parts. The first part presents some controls that the antivirus providers could implement in order to mitigate some (not all of them) of the bypass techniques presented in previous chapters.

To mitigate the DLL hijacking vulnerability (a DLL is loaded using his name; see chapter 3 for more explanations) a proper mechanism to validate the loaded DLL module should be implemented. This validation should use not only the DLL name but also by a certificate and a signature.

To mitigate the Unquoted service path vulnerability (an executable path contains spaces and the path is not enclosed within quotation marks; see chapter 3 for more explanation) the solution is simply to wrap quotation marks around the executable path of the service.

For improving the antivirus detection the authors are proposing to use “dynamic” YARA. The goal of the “dynamic” YARA is to scan for potentially malicious strings and code at the memory level, on a dumped memory snapshot. Normally YARA is used by the static engine for file signature but in the case of “dynamic” YARA the template engine is used to look at the memory where the malware has been already de-obfuscated, unpacked, and decrypted.

Another best practice consists in the usage of Antimalware Scan Interface (AMSI) by the application developers.Windows Antimalware Scan Interface (AMSI) is an API that allows custom applications and services to integrate with any antimalware product that’s present on a machine.

The second part of the chapter contains some secure coding recommendations which can be applicable in SDLC of any type of software: Do not use old code, Input validation (of the AntiVirus UI), Read and fix the compiler warnings, Automated code testing, Use integrity validation for the static signature files download.

How to upload (big) files to Jenkins job as build parameter

Context

Originally, Jenkins had a mechanism to upload files as build parameters but this mechanism was rather faulty (see JENKINS-27413 and JENKINS-29289 ).

A new mechanism was proposed (for Jenkins2 only) via the File Parameter plug-in. The plug-in offers the possibility to capture files as build parameters like this:

def fb64 = input message: 'upload', parameters:  [base64File('file')]
node {
    withEnv(["fb64=$fb64"]) {
        sh 'echo $fb64 | base64 -d'
    }
}

Problem

If you look closer to the File Parameter plug-in documentation it said that: “You can use Base64 parameters for uploading small files in the middle of the build”. What does it means “small files” in terms of size is not mentioned but if you try the previous example with files bigger than 2 kBytes then the job will fail with the following error:

java.io.IOException: error=7, Argument list too long
        at java.lang.UNIXProcess.forkAndExec(Native Method)
        at java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
        at java.lang.ProcessImpl.start(ProcessImpl.java:134)
        at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
...
Caused: java.io.IOException: Cannot run program "nohup" (in directory "/var/jenkins_cache/workspace/testproject"): error=7, Argument list too long

Solution

What is the root cause of this exception ? I’m not exactly sure but I think that  sh echo $fb64 | base64 -dcommand will transfer as environment variable the file to the Jenkins slave executing the job and something into this transfer mechanism is not very robust.

I propose two ways to workaround this problem:

Solution 1: Don’t send the uploaded file as environment variable to sh

Don’t send the uploaded file as environment variable to ‘sh‘ and write the file directly into the workspace:

withEnv(["fb64=$fb64"]) {
    script{
        def  decoded = new String(fb64.decodeBase64())
        writeFile file:"uploaded_file.txt", text: decoded
        sh 'cat ${WORKSPACE}/uploaded_file.txt'
    }

The drawback of this solution is that you’ll have to write the uploaded file somewhere into your workspace, so if you want to store it into another location then you’ll have to add some extra steps to the pipeline.

Solution 2: Don’t use withEnv pipeline step

The second solution is not using the withEnv pipeline step and just directly use the sh echo $fb64 | base64 -dcommand from a script step:

script{
         sh "set +x; echo '$fb64' | base64 -d > /tmp/uploaded_file.txt"
         cat '/tmp/uploaded_file.txt'
      }

Please note that I’m using the “set +x” before the echo command in order to inhibit the output of the command so the Jenkins console/log is not filled-in with base64 encoded characters. Also in this solution you have the freedom to chose the destination of the uploaded file.