badtech.orghttps://www.badtech.org/2015-02-12T17:54:00-05:00Remote Screenshots of an Xorg Session using PHP and Apache2015-02-12T17:54:00-05:00Adam Fieldtag:www.badtech.org,2015-02-12:remote-xorg-screenshots.html<p>This is useful for kiosks and information displays. I use this for monitoring digital signage. It makes screenshots available through a PHP script on an Apache webserver. It doesn't take into account any security or access control, which is left as an exercise for the user. The <code>xwd</code> tool captures images from the running X server for you. You shouldn't use this to spy on users, however it does demonstrate just how much power the <code>root</code> user has on a multiuser system.</p>
<p>The basis for this article is allowing the <code>xwd</code> command to be executed under the context of the user running the X session.</p>
<p>The <code>xwd</code> output format is a raw <em>"specially formatted dump file"</em>. Gimp can open these files, and more importantly, so can ImageMagick. We'll use the Imagick PHP extension to convert the .xwd format. </p>
<p>This how-to is Debian specific, but packages for your distro should be similarly named.</p>
<h2>Packages:</h2>
<div class="highlight"><pre>apache2
php5
php5-imagick
</pre></div>
<h2>Sudo:</h2>
<p>We want to run commands as the X user, from the user running the apache daemon. In this case, Debian runs apache2 as the <code>www-data</code> user. If you're unsure, while apache is running execute <code>ps aux | grep apache</code> and the username will be in the first column.</p>
<p>The <code>sudo</code> <em>(substitute user do)</em> command allows you to run commands as another user. The typical, preconfigured use for this command is to run commands as root. To get the functionality we want you'll have to edit the <code>/etc/sudoers</code>. <em>Do not edit this file directly!</em> There is a special tool for this, enter <code>visudo</code>.</p>
<div class="highlight"><pre>visudo edits the sudoers file in a safe fashion, analogous to vipw(8). visudo
locks the sudoers file against multiple simultaneous edits, provides basic
sanity checks, and checks for parse errors. If the sudoers file is currently
being edited you will receive a message to try again later.
</pre></div>
<p>On Debian, <code>visudo</code> respects your editor choice, which you can configure with <code>update-alternatives --config editor</code>. Here are the basics you will add to your <code>/etc/sudoers</code> using <code>visudo</code>.</p>
<div class="highlight"><pre># Cmnd alias specification
Cmnd_Alias SCREENSHOT_CMD = /usr/bin/xwd
</pre></div>
<p>This directive just places the binary <code>xwd</code> into an alias group we have named <code>SCREENSHOT_CMD</code>. Next is the user details, typically under <code>User privilege specification</code>:</p>
<div class="highlight"><pre>www-data ALL=(fred) NOPASSWD: SCREENSHOT_CMD
</pre></div>
<p>The above directive allows <code>www-data</code>, the apache user, to run the alias SCREENSHOT_CMD as user <code>fred</code>. Fred is the target user who will be running the xsession, substitute as needed. The <code>ALL=</code> prefix matches the machine hostname, if you were to distribute this file on your network. The <code>NOPASSWD</code> directive allows <code>www-data</code> to enter no password to run <code>xwd</code>. The <code>www-data</code> user typically won't have a password set anyways, it is blocked from normal logins.</p>
<h2>Test sudo:</h2>
<p>Now we can test the sudoers setup. If you are <code>root</code>, execute <code>su - www-data</code>. If you are not, execute <code>sudo su - www-data</code>. You will have basic terminal as the <code>www-data</code> user. Remember the apache2 binary is executed as the <code>www-data</code> user on Debian, but is dependent on distribution. Test <code>xwd</code>:</p>
<div class="highlight"><pre>root@debian:~# su - www-data
$ whoami
www-data
$ sudo -u fred xwd -display :0.0 -help
usage: xwd [-display host:dpy] [-debug] [-help] [{-root|-id <id>|-name <name>}] [-nobdrs] [-out <file>] [-xy] [-add value] [-frame]
</pre></div>
<p>It worked! Note that this command with only work with <code>fred</code> logged in and running an X session. The <code>-display</code> is needed as you don't have the environmental variable $DISPLAY set. The <code>:0.0</code> argument meant means localhost (no host specified before the colon), first screen on first display. You can of course set the DISPLAY variable yourself, but you'll have to do it every time you sudo as the <code>www-data</code> user doesn't have any amenities to help you. If you have a more complicated X setup, you'll have to adjust for it here.</p>
<p>After testing, you will have to restart your apache daemon. It won't have the proper sudo permissions updated until the <code>www-data</code> logs in again.</p>
<h2>PHP:</h2>
<p>The following is a quick PHP script to convert and output the xwd screenshot as a JPEG image. The default webroot on Debian is <code>/var/www</code>, where we will be placing this file.</p>
<div class="highlight"><pre><span class="cp"><?php</span>
<span class="c1">// screen_shot.php</span>
<span class="c1">// Take a screen shot of the X server and serve as a JPG</span>
<span class="cm">/*</span>
<span class="cm"> WARNING: You are serving copies of your X display to anyone who asks for them! </span>
<span class="cm">*/</span>
<span class="c1">// You can set a DISPLAY variable if you like</span>
<span class="c1">//putenv("DISPLAY=:0.0");</span>
<span class="c1">// Target user</span>
<span class="nv">$x_user</span> <span class="o">=</span> <span class="s1">'fred'</span><span class="p">;</span>
<span class="c1">// Execute the screenshot (specifying the display)</span>
<span class="c1">// -root</span>
<span class="c1">// "This option indicates that the root window should be selected for the</span>
<span class="c1">// window dump, without requiring the user to select a window with the pointer."</span>
<span class="c1">// -silent</span>
<span class="c1">// "Operate silently, i.e. don't ring any bells before and after dumping the window."</span>
<span class="nv">$xwd</span> <span class="o">=</span> <span class="nb">shell_exec</span><span class="p">(</span><span class="s2">"sudo -u </span><span class="si">{</span><span class="nv">$x_user</span><span class="si">}</span><span class="s2"> xwd -root -silent -display :0.0"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$xwd</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$im</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Imagick</span><span class="p">();</span>
<span class="c1">// Import the binary captured from the screen</span>
<span class="nv">$im</span><span class="o">-></span><span class="na">readImageBlob</span><span class="p">(</span><span class="nv">$xwd</span><span class="p">);</span>
<span class="c1">// Ajdust to taste</span>
<span class="nv">$im</span><span class="o">-></span><span class="na">setCompression</span><span class="p">(</span><span class="mi">70</span><span class="p">);</span>
<span class="nv">$im</span><span class="o">-></span><span class="na">setImageFormat</span><span class="p">(</span><span class="s1">'jpeg'</span><span class="p">);</span>
<span class="c1">// Output directly, image doesn't need to hit the disk</span>
<span class="nb">header</span><span class="p">(</span><span class="s1">'Content-type: image/jpg'</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$im</span><span class="p">;</span>
<span class="p">}</span>
<span class="cp">?></span>
</pre></div>
<p>Check on it with your browser and you should have the remote X display:</p>
<h2>Success:</h2>
<p><img alt="Remote X Screenshot" src="https://www.badtech.org/images/linux-remote-screenshot-screenshot.png" /></p>
<h2>Remember:</h2>
<p>"Don't be evil."</p>Netbooting Finnix using Dnsmasq2015-02-07T12:54:00-05:00Adam Fieldtag:www.badtech.org,2015-02-07:netboot-finnix-using-dnsmasq.html<p><a href="http://www.finnix.org/">Finnix</a> is a great Linux live CD, based on Debian. It does not include an X server, and clocks in around 100MB in size (compressed).</p>
<p><a href="http://www.thekelleys.org.uk/dnsmasq/doc.html">Dnsmasq</a> is a combination DNS, DHCP, TFTP service for small and medium size LANs. </p>
<h2>Resources (RTFM):</h2>
<ul>
<li><a href="http://www.finnix.org/Netboot">http://www.finnix.org/Netboot</a></li>
<li><a href="http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html">http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html</a></li>
</ul>
<h2>How-to:</h2>
<p>To start, get Finnix booted up. I used a virtual machine on Oracle VirtualBox with no virtual hard disk, 1GB ram, and 64bit OS setting. I used bridge mode networking as a preference; it won't matter here to make the boot image, but will be needed for testing later. You could use real hardware just as well,but you will need a way to copy files from the running OS (network, USB, etc). Why start Finnix? It has a tool to build the initrd you need, hassle-free. After you get Finnix started, run:</p>
<div class="highlight"><pre>finnix-netboot-server
</pre></div>
<p><img alt="finnix-netboot-server" src="https://www.badtech.org/images/finnix-netboot-server-640480.png" /></p>
<p>I chose to make the large initrd, thereby requiring no NFS exports for booting the distro - but requiring at least 512MB of RAM for machines that will boot it. The tool will output some details about ISC DHCP server configuration, but since we're using dnsmasq we'll just ignore that.</p>
<p>The files Finnix create are now in <code>/srv/tftp/finnix</code>.</p>
<p>I used scp to bring them over to my machine running dnsmasq.</p>
<div class="highlight"><pre>scp -r /srv/tftp/finnix user@your-dnsmasq-host:/srv/tftp/
</pre></div>
<p>The destination folder should now have this tree:</p>
<div class="highlight"><pre>/srv/tftp/finnix
+-- [4.0K] /srv/tftp/finnix/boot
| +-- [4.0K] /srv/tftp/finnix/boot/grub
| | +-- [1.0K] /srv/tftp/finnix/boot/grub/loopback.cfg
| +-- [4.0K] /srv/tftp/finnix/boot/x86
| +-- [2.0K] /srv/tftp/finnix/boot/x86/boot.cat
| +-- [ 339] /srv/tftp/finnix/boot/x86/boot.msg
| +-- [ 20K] /srv/tftp/finnix/boot/x86/chain.c32
| +-- [1.1M] /srv/tftp/finnix/boot/x86/dos.imz
| +-- [1.0K] /srv/tftp/finnix/boot/x86/f1
| +-- [1.1K] /srv/tftp/finnix/boot/x86/f2
| +-- [333K] /srv/tftp/finnix/boot/x86/hdt.c32
| +-- [136M] /srv/tftp/finnix/boot/x86/initrd_net.xz
| +-- [2.4M] /srv/tftp/finnix/boot/x86/initrd.xz
| +-- [359K] /srv/tftp/finnix/boot/x86/ipxe
| +-- [2.4M] /srv/tftp/finnix/boot/x86/linux
| +-- [3.5M] /srv/tftp/finnix/boot/x86/linux64
| +-- [ 26K] /srv/tftp/finnix/boot/x86/memdisk
| +-- [158K] /srv/tftp/finnix/boot/x86/memtest
| +-- [ 54K] /srv/tftp/finnix/boot/x86/menu.c32
| +-- [210K] /srv/tftp/finnix/boot/x86/pci.ids
| +-- [4.0K] /srv/tftp/finnix/boot/x86/pxelinux
| | +-- [ 26K] /srv/tftp/finnix/boot/x86/pxelinux/pxelinux.0
| | +-- [3.5K] /srv/tftp/finnix/boot/x86/pxelinux/template.cfg
| +-- [ 16K] /srv/tftp/finnix/boot/x86/sbm.imz
| +-- [ 17K] /srv/tftp/finnix/boot/x86/splash.png
| +-- [149K] /srv/tftp/finnix/boot/x86/vesamenu.c32
+-- [ 26K] /srv/tftp/finnix/pxelinux.0
+-- [4.0K] /srv/tftp/finnix/pxelinux.cfg
+-- [3.5K] /srv/tftp/finnix/pxelinux.cfg/default
</pre></div>
<h2>Configuring Dnsmasq:</h2>
<p>Typically the configuration is located somewhere typically like <code>/etc/dnsmasq.conf</code>.</p>
<p>Directives you'll be interested in:</p>
<div class="highlight"><pre># Set the boot filename for netboot/PXE. You will only need
# this is you want to boot machines over the network and you will need
# a TFTP server; either dnsmasq's built in TFTP server or an
# external one. (See below for how to enable the TFTP server.)
dhcp-boot=pxelinux.0
# Enable dnsmasq's built-in TFTP server
enable-tftp
# Set the root directory for files available via FTP.
tftp-root=/srv/tftpd/finnix
</pre></div>
<p>The default <code>pxelinux.0</code> boot file is fine, as the Finnix tool names it that for you. The path setting is up to you: I used <code>/srv/tftp/finnix</code> for my install. The <code>tftp-secure</code> option is also your choice. If you enable it, the user running dnsmasq will have to own the files in your tftp root. On Debian 7, the user running the daemon is <code>dnsmasq</code>. Either way, make sure dnsmasq has permission to read the files.</p>
<h2>Check your config:</h2>
<div class="highlight"><pre>root@charon:~# dnsmasq --test
dnsmasq: syntax check OK.
</pre></div>
<h2>Firewall:</h2>
<p>If you are running an iptables firewall (and I hope you are!), you need to allow incoming UDP port 69 traffic. The general syntax for this is (modify with your configuration):</p>
<div class="highlight"><pre><span class="x">iptables -A INPUT -i </span><span class="p">$</span><span class="nv">lan</span><span class="x"> -s </span><span class="p">$</span><span class="nv">your_net</span><span class="x"> -p udp --dport 69 -J ACCEPT</span>
<span class="x">// you probably have something like this rule established already:</span>
<span class="x">iptables -A INPUT -i </span><span class="p">$</span><span class="nv">lan</span><span class="x"> -s </span><span class="p">$</span><span class="nv">your_net</span><span class="x"> -m state --state RELATED,ESTABLISHED -j ACCEPT</span>
</pre></div>
<p>I have another rule in my setup that allows state RELATED, ESTABLISHED state traffic in from my LAN, too. The RELATED rule works with dnsmasq's tftpd implementation. Otherwise you would have to manually select a port range in the dnsmasq.conf and allow a big incoming range in the INPUT table. Beyond using <code>-m state</code>, there is also an <code>ip_conntrack_tftp</code> module that I did not test. The conntrack module will offer more fine-grained control. If you wanted to go ahead with a specific range anyways:</p>
<div class="highlight"><pre>tftp-port-range=<start>,<end>
A TFTP server listens on a well-known port (69) for connection initiation, but it also uses a dynamically-allocated port for each connection. Normally these are allocated by the OS, but this option specifies a range of ports for use by TFTP transfers. This can be useful when TFTP has to traverse a firewall. The start of the range cannot be lower than 1025 unless dnsmasq is running as root. The number of concurrent TFTP connections is limited by the size of the port range.
</pre></div>
<p>If you use this, you will have to allow the incoming connection on your selected port range. But, as previously mentioned, the RELATED state rule knows what to do without specifying ports.</p>
<h2>Testing:</h2>
<p>I like to <code>tail -f /var/log/syslog</code> on the dnsmasq host to watch the log file when testing for the first time. It can help track down path errors and the like. A few errors are normal and seem to be inherent to the Finnix netboot. This is a log of a successful netboot from my machine:</p>
<div class="highlight"><pre>Feb 7 09:35:26 charon dnsmasq[9697]: started, version 2.62 cachesize 150
Feb 7 09:35:26 charon dnsmasq[9697]: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack
Feb 7 09:35:26 charon dnsmasq-dhcp[9697]: DHCP, IP range 10.21.42.100 -- 10.21.42.200, lease time 12h
Feb 7 09:35:26 charon dnsmasq-tftp[9697]: TFTP root is /srv/tftp/finnix secure mode
Feb 7 09:35:26 charon dnsmasq[9697]: using local addresses only for domain frodo
Feb 7 09:35:26 charon dnsmasq[9697]: reading /etc/resolv.dnsmasq.conf
Feb 7 09:35:26 charon dnsmasq[9697]: using nameserver 8.8.4.4#53
Feb 7 09:35:26 charon dnsmasq[9697]: using nameserver 8.8.8.8#53
Feb 7 09:35:26 charon dnsmasq[9697]: using local addresses only for domain frodo
Feb 7 09:35:26 charon dnsmasq[9697]: read /etc/hosts - 6 addresses
Feb 7 09:35:51 charon dnsmasq-dhcp[9697]: DHCPDISCOVER(eth0) 08:00:27:4c:6b:18
Feb 7 09:35:51 charon dnsmasq-dhcp[9697]: DHCPOFFER(eth0) 10.21.42.117 08:00:27:4c:6b:18
Feb 7 09:35:53 charon dnsmasq-dhcp[9697]: DHCPREQUEST(eth0) 10.21.42.117 08:00:27:4c:6b:18
Feb 7 09:35:53 charon dnsmasq-dhcp[9697]: DHCPACK(eth0) 10.21.42.117 08:00:27:4c:6b:18
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: error 0 TFTP Aborted received from 10.21.42.117
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: failed sending /srv/tftp/finnix/pxelinux.0 to 10.21.42.117
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: sent /srv/tftp/finnix/pxelinux.0 to 10.21.42.117
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/cf7f3014-0bfc-4c09-a9c2-c889f78a3fee not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/01-08-00-27-4c-6b-18 not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/0A152A75 not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/0A152A7 not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/0A152A not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/0A152 not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/0A15 not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/0A1 not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/0A not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: file /srv/tftp/finnix/pxelinux.cfg/0 not found
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: sent /srv/tftp/finnix/pxelinux.cfg/default to 10.21.42.117
Feb 7 09:35:53 charon dnsmasq-tftp[9697]: sent /srv/tftp/finnix/boot/x86/boot.msg to 10.21.42.117
Feb 7 09:35:54 charon dnsmasq-tftp[9697]: sent /srv/tftp/finnix/boot/x86/vesamenu.c32 to 10.21.42.117
Feb 7 09:35:54 charon dnsmasq-tftp[9697]: sent /srv/tftp/finnix/pxelinux.cfg/default to 10.21.42.117
Feb 7 09:35:54 charon dnsmasq-tftp[9697]: sent /srv/tftp/finnix/boot/x86/splash.png to 10.21.42.117
Feb 7 09:36:10 charon dnsmasq-tftp[9697]: sent /srv/tftp/finnix/boot/x86/linux64 to 10.21.42.117
</pre></div>
<p>We can test netboot on VirtualBox, too. In your virtual machine settings, under system, adjust the boot order to put network at the top - or hit F12 during VM startup and select network. You also need to use bridge mode networking: this way, the VM gets a DHCP lease from dnsmasq and not a NAT address from VirtualBox.</p>
<p>Unfortunately, there isn't an easy way to netboot over wifi - but almost every PC I've run into is netbootable via the onboard NIC (ethernet cable required). </p>